市場シミュレーション(第9回):ソケット(III)
はじめに
前回の「市場シミュレーション(第8回):ソケット(II)」では、ソケットを利用した実用的なアプリケーションの開発を開始しました。その目的は、MetaTrader 5向けのプログラミングにおけるこのツールの使用方法を示すことでした。確かに、MQL5だけを使って直接サーバーを作成することはできません。しかし、ソケットの使用は特定の言語やオペレーティングシステムに依存しないため、MQL5でプログラミングを実装することで、MetaTrader 5でも利用することができます。
しかし、MetaTrader 5プラットフォーム自体の内部的な理由により、インジケーターとソケットを同時に使用することはできません。より正確に言えば、インジケーターのコード内にソケット関連の処理呼び出しを配置することはできないのです。もしそれが可能であれば、インジケーターをフリーズさせたり、実行される計算のパフォーマンスを損なったりする可能性があるためです。
とはいえ、他の目的でインジケーターを使用することが妨げられているわけではありません。実際、前回の記事では、コントロールやテキストパネルを含むミニチャット全体をインジケーター内に作成しました。インジケーター内で作成されて配置されたこれらの要素は、実行の流れに一切干渉しません。一方で、インジケーターを使用しなければ、前回の記事でおこなったことを実現するのは非常に複雑になり、描画されている資産チャートの一部に干渉してしまうでしょう。
この方法であれば、そのような問題は発生せず、ミニチャットを独立したウィンドウ内に隔離することができます。
とはいえ、前回の記事で生成された実行ファイルは、ミニチャットを実際に機能させるための要件をまだ満たしていません。さらにいくつかの詳細を実装する必要があります。そこで本記事では、MetaTrader 5でミニチャットを動作させるために必要なサポートの作成を仕上げていきます。それでもなお、冒頭で述べたとおり、MQL5は現時点ですべての要件を満たすことができません。そのため、外部プログラミングに頼る必要があります。ただし、この段階でも実行できることがあります。これについては、本記事の後半で説明します。まずは、MQL5でおこなう部分を完成させましょう。
接続クラスの実装
ミニチャットを実際に機能させる最終的なEAのソースコードを見る前に、接続クラスを実装しているヘッダファイルを確認しておきましょう。以下が完全なコードです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class C_Connection 05. { 06. private : 07. int m_Socket; 08. char m_buff[]; 09. string m_info; 10. bool m_NewLine; 11. public : 12. //+------------------------------------------------------------------+ 13. C_Connection(string addr, ushort port, ushort timeout = 1000) 14. :m_Socket(INVALID_HANDLE), 15. m_info(""), 16. m_NewLine(true) 17. { 18. if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError()); 19. else if (SocketConnect(m_Socket, addr, port, timeout)) return; 20. else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError()); 21. SetUserError(1); 22. } 23. //+------------------------------------------------------------------+ 24. ~C_Connection() 25. { 26. SocketClose(m_Socket); 27. } 28. //+------------------------------------------------------------------+ 29. bool ConnectionWrite(string szMsg) 30. { 31. int len = StringToCharArray(szMsg, m_buff) - 1; 32. 33. if (m_Socket != INVALID_HANDLE) 34. if (len >= 0) 35. if (SocketSend(m_Socket, m_buff, len) == len) 36. return true; 37. Print("Connection Write: FAILED..."); 38. 39. return false; 40. } 41. //+------------------------------------------------------------------+ 42. const string ConnectionRead(ushort timeout = 500) 43. { 44. int ret; 45. 46. if (m_NewLine) 47. { 48. m_info = ""; 49. m_NewLine = false; 50. } 51. ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout); 52. if (ret > 0) 53. { 54. m_info += CharArrayToString(m_buff, 0, ret); 55. for (ret--; (ret >= 0) && (!m_NewLine); ret--) 56. m_NewLine = (m_buff[ret] == '\n') || (m_buff[ret] == '\r'); 57. } 58. 59. return (m_NewLine ? m_info : ""); 60. } 61. //+------------------------------------------------------------------+ 62. }; 63. //+------------------------------------------------------------------+
ヘッダファイル:C_Study.mqh
このコードのいくつかの重要な点にご注目ください。これらを理解することで、ミニチャットをどのように動作させるかをより深く理解できるようになります。このクラスには、07行目から10行目の間に示されているように、privateとして宣言されたグローバル変数が含まれています。これらの変数はすべてprivate句の後に記述されている点に注意してください。
そのため、これらの変数にはクラスの外部からアクセスすることはできません。このクラスを使用する際に、最初におこなうことはコンストラクタの呼び出しです。したがって、最初に実行される行は13行目です。ここで14行目、15行目、16行目を見ると、いくつかの処理をおこなっていることが分かります。ここでクラスのグローバル変数が初期化されています。ひとつ補足すると、私は習慣的に、また主にコンストラクタ本体の中で初期化を忘れないようにするため、このような書き方をしています。意外に思われるかもしれませんが、この種の初期化忘れは非常によくあるミスです。この方法を使うことで、主要な変数を正しく初期化し忘れることがないようにしています。
さて、コンストラクタの実行が始まると、18行目で標準ライブラリの呼び出しを使ってソケットの作成を試みます。これが失敗した場合、MetaTrader 5にメッセージを表示します。成功した場合は、19行目でパラメータで指定されたソケットへの接続を試みます。しかし、前の行とは異なり、ここでは成功した場合に即座にreturnします。これは、接続が確立されたことを意味するからです。失敗した場合には、20行目で何が起こったのか、そして失敗の理由が通知されます。
いずれにしても、ソケットの作成または接続のいずれかが不可能であった場合、21行目が実行され、メインコード、つまりこの場合はEAに対して接続処理が失敗したことを通知できるようになります。EAコードを見る際に、これをどのように検出できるかを説明します。21行目は、接続試行プロセス中に失敗が発生した場合にのみ実行されます。
24行目にあるデストラクタの目的はひとつだけです。それはソケットを閉じることです。このため、本体には1行しかありません。それが26行目で、作成されていれば使用中のソケットを解放します。
次に、このクラスの2つの主要な関数を見ていきましょう。最初は接続を通じてデータを送信する役割を持つ関数です。そうです、29行目にある書き込み関数のことです。この関数は次に説明する関数と同様に、注意深く実装する必要があります。なぜなら、画像、音声、動画、あるいはここでおこなうようなテキストなど、何を送信する場合であっても、残りのコードは一切変更されないからです。実際に変わるのは、この2つの関数、すなわち書き込みと読み込みだけです。ここでは、可能な限りシンプルな方法で処理をおこなっています。そのため、情報送信時に暗号化やその他のセキュリティ処理は一切おこなっていません。理由は単純で、ソケットの動作をテストし、学習している段階だからです。
実際に送信するのはテキストのみであるため、最初におこなうべきことは、送信する文字数を決定することです。これは31行目でおこなわれています。文字数から1を引いているのは、受信側の文字列がC/C++の標準に従い、ヌル文字で終端されるからです。このヌル文字は送信しません。コンストラクタは作成時または接続時に失敗している可能性があるため、何かを送信しようとする前にソケットが有効かどうかを確認する必要があります。このチェックは33行目でおこなわれます。何らかの理由でソケットが無効であれば、送信はおこなわれません。
これが確認できたら、もうひとつテストをおこなう必要があります。それは、転送する文字数のチェックです。送信するものが何もないのに送信を試みるのは意味がないからです。このテストは34行目でおこなわれます。これらすべてのテストに合格した場合、35行目でオープンされたソケットを通じて目的のデータの送信を試みます。この場合、通常の接続を使用しており、暗号化や認証処理はおこなっていません。ただし、MQL5はセキュアな通信もサポートしています。この点については、ドキュメントを参照して理解を深めてください。
データの送信に成功した場合、36行目でtrueが呼び出し元に返されます。いずれかのテストに失敗した場合は、37行目でMetaTrader 5にメッセージが表示され、39行目でfalseが呼び出し元に返されます。
次に、42行目にある読み込み関数を見ていきましょう。ここで少し立ち止まって説明しておきたいことがあります。各書き込み関数には、それに対応する読み込み関数があるべきです。これは必須ではありませんが、次のような状況を考えてみてください。異なる種類のデータを送信するために、複数の送信関数を作成する場合、それぞれに対応した受信関数が存在するのが適切です。
言い換えれば、テキストを読むために設計された関数で、動画データを読み込もうとするでしょうか。それは意味がありません。そのため、送信されるデータの種類と、使用されるプロトコルの種類の両方において、常に正しく対応付けるようにしてください。
このプロトコル部分は、TCPやUDP接続そのものとは関係なく、送信前に情報がどのように構造化されているかに関係します。ソケット内のデータは、読み込んでいるファイルのようなものだと常に考えてください。その内容を正しく理解するためには、正しい読み込みプロトコルを使用する必要があります。
JPEG画像を読み込むためのプロトコルでビットマップを読み込もうとしても意味がありません。どちらも画像ではありますが、ファイル内部での構造はまったく異なります。ソケットにおいても同様です。この注意点を踏まえたうえで、ソケットの読み込みルーチンに進みましょう。
「市場シミュレーション(第7回):ソケット(I)」の記事で見たように、サーバー側でエコーを生成していた場合とは異なり、ここでは必ずしもサーバーの応答を待ち続けるわけではありません。実際には、短い時間だけ応答を待ちます。重要なのは、応答が速くても遅くても、ソケットからメッセージ全体を読み終える前であっても、呼び出し元に制御を返すという点です。
では、ソケットを完全に読み取る前にEAコードへ戻ることができるのでしょうか。できます。ただし、そのためにはいくつかの注意が必要です。先ほど、ソケット上で期待される情報の種類を正しく扱う必要があると述べたのは、このためです。この読み込み関数では、デフォルトで500ミリ秒待ってから戻るようになっています。この時間が経過すると、たとえソケット内に情報が存在しなくても、メインコードに戻ります。
しかし、この間に何らかの情報が到着した場合、51行目でそれを読み込みます。ここで、自分自身のサーバーを実装しようとしている場合に重要となる点に注意してください。54行目では、ソケット内に存在するすべての内容を戻り値用の文字列に追加しています。ただし、この戻り値の文字列が呼び出し元に返されるのは、文字列内に改行文字またはキャリッジリターン文字が見つかった場合に限られます。これを確認するために、55行目のループを使用して、受信した内容を走査し、これらの文字を探しています。
これらの文字のいずれかが見つかると、m_NewLine変数がtrueになります。その結果、59行目でソケットから読み込んだ内容が呼び出し元に返されることが許可されます。同様に、新たな呼び出しがおこなわれた際には、46行目のテストによって文字列内容がクリアされ、新しいサイクルが開始されます。
ここで重要な点に注意してください。29行目にあるソケットの書き込み関数のコードでは、読み込み関数が期待する文字は追加していません。OBJ_EDITオブジェクトも、そのような文字を追加しません。同様に、ConnectionWrite関数を呼び出すコードも追加していません。では、誰がこれらの文字を追加しているのでしょうか。それはサーバーです。したがって、自分自身のサーバーを実装する場合には、必ずこれらの文字を追加しなければなりません。そうしないと、ミニチャットは投稿されたメッセージ同士を区別できなくなります。
このようなやり方は、少し無意味あるいは馬鹿げているように見えるかもしれません。しかし、この方法を採用することで、本記事に含まれている動画で確認できるような動作を実現できます。さて、それでは、接続クラスのコードを確認したところで、次にEAコードを見ていきましょう。
EAの実装
以下は、ミニチャットを実装するために使用される完全なEAコードです。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Mini Chat for demonstration of using sockets in MQL5." 04. #property link "https://www.mql5.com/pt/articles/12673" 05. #property version "1.00" 06. //+------------------------------------------------------------------+ 07. #define def_IndicatorMiniChat "Indicators\\Mini Chat\\Mini Chat.ex5" 08. #resource "\\" + def_IndicatorMiniChat 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Mini Chat\C_Connection.mqh> 11. #include <Market Replay\Defines.mqh> 12. //+------------------------------------------------------------------+ 13. input string user00 = "127.0.0.1"; //Address 14. input ushort user01 = 27015; //Port 15. //+------------------------------------------------------------------+ 16. long gl_id; 17. int gl_sub; 18. C_Connection *Conn; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. Conn = new C_Connection(user00, user01); 23. if (_LastError > ERR_USER_ERROR_FIRST) 24. return INIT_FAILED; 25. ChartIndicatorAdd(gl_id = ChartID(), gl_sub = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_IndicatorMiniChat)); 26. 27. EventSetTimer(1); 28. 29. return INIT_SUCCEEDED; 30. } 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. EventKillTimer(); 35. delete Conn; 36. ChartIndicatorDelete(gl_id, gl_sub, ChartIndicatorName(gl_id, gl_sub, 0)); 37. } 38. //+------------------------------------------------------------------+ 39. void OnTick() 40. { 41. } 42. //+------------------------------------------------------------------+ 43. void OnTimer() 44. { 45. string sz0 = (*Conn).ConnectionRead(); 46. if (sz0 != "") 47. EventChartCustom(gl_id, evChatReadSocket, 0, 0, sz0); 48. } 49. //+------------------------------------------------------------------+ 50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 51. { 52. if (id == CHARTEVENT_CUSTOM + evChatWriteSocket) 53. (*Conn).ConnectionWrite(sparam); 54. } 55. //+------------------------------------------------------------------+
EAのソースコード
コードは非常にシンプルであることに注意してください。実際、ここではそれほど多くのプログラミングは必要ありません。それでも、いくつか注意すべき点があります。07行目と08行目を見ると、前回の記事で作成したインジケーターをEAコードに追加していることが分かります。この方法により、EAコードがコンパイルされると、インジケーターの実行ファイルは不要になり、削除することができます。なぜなら、その内容はEAの中に埋め込まれるからです。この措置の理由については、前回の記事ですでに説明しました。コード全体の流れは、以前の記事で見たものと比べて大きな変更はありません。
今回のポイントは、タイマーイベントを受信するための呼び出しが追加されている点です。これは27行目でおこなわれています。これにより、約1秒ごとにタイマーイベントを処理するコード、つまりOnTimerが呼び出されることになります。このEAコードで本当に重要なのは、まさにこのOnTimer呼び出しとOnChartEvent呼び出しです。この2つが、現在の実装方法においてミニチャットを機能させている要素です。まずはOnTimer手続きを理解しましょう。
MetaTrader 5がOnTimerイベントを発生させるたびに、EAがこの呼び出しを受け取ります。そして45行目で、13行目と14行目で指定されたソケットから読み込みを試みます。そうです、ここでネットワークへの接続をおこなっています。この点については、ソケットを扱った2つ前の記事ですでに説明しました。これについて疑問がある場合は、「ソケット(I)」の記事をお読みください。
さて、読み込み処理は文字列を返す場合もあれば、何も返さない場合もあります。読み込み関数から何らかの値が返された場合、46行目のテストが成功します。その場合、47行目に示されているカスタムイベントが発生します。このイベントの目的は、ミニチャット用インジケーターがソケットに投稿された情報へアクセスできるようにすることです。もしミニチャットがEAの中に直接実装されていれば、このようなカスタムイベントはまったく不要でしょう。しかし、今回は役割を分離しているため、インジケーター側でテキストパネルへの表示処理をおこなえるように、データを送る必要があります。このように、何を表示したいのかによって、最適な実装方法に適応する必要がある点が、とても興味深いところです。
OnTimer手続きに関しては、これ以上の詳細はありません。本当にこれだけです。同様に、OnChartEvent手続きも非常にシンプルです。今回は、ミニチャット用インジケーターからソケットへ書き込みをおこなう要求を受け取ります。そのような呼び出しかどうかを確認するために、52行目のテストを使用します。カスタムイベントであることが確認されると、53行目で、ミニチャットインジケーターから提供されたデータが書き込み関数へ渡されます。
この通信システムには、イベント駆動型プログラミングに慣れていない人には少し分かりにくい点があります。それは、もし他のプログラム(別のインジケーター、スクリプト、あるいはサービスであっても)が、evChatReadSocketと同じ値を持つカスタムイベントを発生させた場合、ミニチャットインジケーターは、それをEAから来たものとして解釈してしまうという点です。同様に、evChatWriteSocketと同じ値を持つカスタムイベントが発生した場合、EAはそれをミニチャットインジケーターからのものとして解釈します。
この仕組みがもたらす可能性にお気づきでしょうか。特に、ソケットを通じてデータを送信する先のアドレスやポートを知っており、同様に特定のアドレスやポートからデータを読み込むことができる場合、この仕組みは非常に強力です。この種のメカニズムは、特定の場所で何が起きているかを観察する小さな「スパイ」を作る可能性さえ生み出します。そのため、実際の運用では、常に適切な暗号化やエンコードをおこなう必要があります。しかし、ここではあくまでデモンストレーション目的で使用しているため、データ漏洩のリスクは最小限、ほぼゼロです。動画で確認できるように、実際のネットワークが存在しなくても、ネットワーク接続をテストすることができます。
ここまでの内容はすべてMQL5の範囲内ですので、多くの方はこの結果に満足し、自分自身でサーバーを実装できるようになるでしょう。しかし、サーバーの実装方法を知らない方のために、次のセクションでは、純粋に興味本位として、その部分を見ていくことにします。
ミニチャット用サーバーの実装
サーバーの実装は、決して複雑な作業ではありません。実際のところ、非常にシンプルです。ただし、サーバーを実装することと、それを運用することは別の話です。実装とはコードを作成することを指します。一方で運用とは、本来公開されるべきでない情報を一切漏らさないようにすることを意味します。
ここでは、サーバーを運用する際の実務的な側面については扱いません。純粋に実装部分のみを取り上げます。ただし、ここで示すコードは、本番環境で使用すべきものではないという点を明確にしておきます。提示するコードは教育目的のものです。言い換えれば、内容を完全に理解しないまま使用してはいけません。あくまで、サーバーが実際にどのように実装されるのかを学び、理解するための手段としてのみ使用してください。完全なコードを以下に示します。
001. #define WIN32_LEAN_AND_MEAN 002. 003. #include <winsock2.h> 004. #include <iostream> 005. #include <sstream> 006. #include <stdlib.h> 007. 008. #pragma comment (lib, "Ws2_32.lib") 009. 010. #define DEFAULT_BUFLEN 256 011. #define DEFAULT_PORT 27015 012. 013. using namespace std; 014. 015. int __cdecl main(void) 016. { 017. WSADATA wsData; 018. SOCKET listening, slave; 019. sockaddr_in hint; 020. fd_set master; 021. bool Looping = true; 022. int ConnectCount; 023. string szMsg; 024. 025. if (WSAStartup(MAKEWORD(2, 2), &wsData) != EXIT_SUCCESS) 026. { 027. cout << "Can't Initialize WinSock! Quitting..." << endl; 028. return EXIT_FAILURE; 029. } 030. if ((listening = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) 031. { 032. cerr << "Can't create a socket! Quitting" << endl; 033. WSACleanup(); 034. return EXIT_FAILURE; 035. } 036. else 037. cout << "Creating a Socket..." << endl; 038. 039. hint.sin_family = AF_INET; 040. hint.sin_port = htons(DEFAULT_PORT); 041. hint.sin_addr.S_un.S_addr = INADDR_ANY; 042. 043. if (bind(listening, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR) 044. { 045. cout << "Bind failed with error:" << WSAGetLastError() << endl; 046. WSACleanup(); 047. return EXIT_FAILURE; 048. } 049. else 050. cout << "Bind success..." << endl; 051. 052. if (listen(listening, SOMAXCONN) == SOCKET_ERROR) 053. { 054. cout << "Listen failed with error:" << WSAGetLastError() << endl; 055. WSACleanup(); 056. return EXIT_FAILURE; 057. } 058. else 059. cout << "Listen success..." << endl; 060. 061. FD_ZERO(&master); 062. FD_SET(listening, &master); 063. cout << "Waiting for client connection" << endl; 064. 065. while (Looping) 066. { 067. fd_set tmp = master; 068. 069. ConnectCount = select(0, &tmp, nullptr, nullptr, nullptr); 070. for (int c0 = 0; c0 < ConnectCount; c0++) 071. { 072. if ((slave = tmp.fd_array[c0]) == listening) 073. { 074. SOCKET NewClient = accept(listening, nullptr, nullptr); 075. 076. szMsg = "Sent by SERVER: Welcome to Mini Chart\r\n"; 077. FD_SET(NewClient, &master); 078. send(NewClient, szMsg.c_str(), (int)szMsg.size() + 1, 0); 079. cout << "Client #" << NewClient << " connecting..." << endl; 080. } 081. else 082. { 083. char buff[DEFAULT_BUFLEN]; 084. int bytesIn; 085. 086. ZeroMemory(buff, DEFAULT_BUFLEN); 087. if ((bytesIn = recv(slave, buff, DEFAULT_BUFLEN, 0)) <= 0) 088. { 089. closesocket(slave); 090. cout << "Client #" << slave << " disconnected..." << endl; 091. FD_CLR(slave, &master); 092. } 093. else 094. { 095. if (buff[0] == '\\') //Check command ... 096. { 097. szMsg = string(buff, bytesIn); 098. 099. if (szMsg == "\\shutdown") 100. { 101. Looping = false; 102. break; 103. } 104. continue; 105. } 106. for (u_int c1 = 0; c1 < master.fd_count; c1++) 107. { 108. SOCKET out = master.fd_array[c1]; 109. 110. if ((out != listening) && (out != slave)) 111. { 112. ostringstream s1; 113. 114. s1 << "Client #" << slave << ": " << buff << "\r\n"; 115. send(out, s1.str().c_str(), (int)s1.str().size() + 1, 0); 116. } 117. } 118. } 119. 120. } 121. } 122. } 123. 124. FD_CLR(listening, &master); 125. closesocket(listening); 126. szMsg = "Server is shutting down. Goodbye\r\n"; 127. while (master.fd_count > 0) 128. { 129. slave = master.fd_array[0]; 130. send(slave, szMsg.c_str(), (int)szMsg.size() + 1, 0); 131. FD_CLR(slave, &master); 132. closesocket(slave); 133. } 134. 135. WSACleanup(); 136. 137. return EXIT_SUCCESS; 138. }
サービスのソースコード
このサーバーコードに関するひとつの注意点として、コード自体はC++で書かれていますが、コンパイルにはVisual Studioのツールを使用する必要があります。ただし、何がおこなわれているのかを理解していれば、GCCでコンパイルできるように修正することも可能です。とはいえ、このコード自体に固執しすぎないでください。本当に重要なのは、その動作原理を理解することです。
そこで、C++やC/C++によるソケットプログラミングにあまり慣れていない方のために、いくつかの重要なポイントを説明します。01行目と08行目はVisualStudioコンパイラに関連するものです。10行目では、ソケットから読み込むために使用するバッファのサイズを定義しています。より大きな、あるいはより小さなバッファを使用することもできますが、バッファが小さすぎる場合、ソケットから完全な情報を取得するために複数回の読み込みが必要になる点に注意してください。いずれにしても、ここでおこなっていることは純粋に教育目的であるため、256文字のバッファで十分です。
11行目は非常に重要で、使用するポート番号を定義しています。これをコマンドライン引数で指定する方法もありますが、ここでは説明をできるだけ簡単に進めるため、あえて固定値にしています。17行目から23行目の間では、頻繁に使用されるいくつかの変数を宣言しています。すべてのコードをmain関数の中に書いていますが、本来であれば小さなルーチンに分割するのが理想的です。ただし、このサーバーは非常にシンプルなので、main手続き内ですべてを処理しても問題ありません。
ここで注目してほしい点があります。ソケットを設定し、サーバーがポートを監視してクライアントからの接続を受け付けられるようにする各ステップごとに、コンソールへメッセージを出力しています。そのため、ソケットが作成された時点で、37行目にその確認が表示されます。なお、ソケットの作成には2つのステップが必要です。最初にWSAStartupを呼び出し、その後にsocket関数を呼び出します。LinuxなどのシステムではWSAStartupは不要であり、Windows上の一部のソケット実装でも使用されない場合があります。しかし、Windows環境で作業する場合、WSAStartupを使用するのは一般的に良い習慣とされています。
いずれにしても、ソケットは作成されます。その後、39行目から41行目にかけて設定をおこないます。ここでは、使用するポート、監視するアドレス、そしてソケットの種類を指定します。今回は実験目的のため、どのアドレスからの接続も許可しています。特定のアドレスに制限することも可能ですが、サーバーでは通常、すべての接続を許可します。この部分の設定方法を正しく理解しておくことは非常に重要です。設定を誤ると、意図せず危険な状態を招く可能性があるからです。設定が完了したら、43行目でその内容をシステムに通知します。
52行目では、サーバーが同時に受け付ける最大接続数を定義しています。このステップ自体は特に難しいものではありません。ここまでで、サーバーは設定され、接続を受け付ける準備が整います。次に、発生する接続を管理するための構造体を準備します。これは61行目と62行目でおこなわれています。ここでは、すべてのオープン中の接続を保持する動的配列のようなものを作成し、デモ動画で示されているような動作をサーバーがおこなえるようにしています。
これが完了したら、65行目のループに入ります。このループは、デモ動画で示されているように、クライアント側から終了させることができます。このループ内には説明すべき点がいくつかありますが、その大部分は、あるクライアントがソケットに投稿した内容を読み込み、それを他のすべてのクライアントに転送する処理であり、特に目新しいものではありません。
しかし、MQL5コードの説明中に、キャリッジリターンや改行文字を含めることが重要であり、クライアント側ではそれらを追加していない、つまりサーバー側で追加していると述べました。ここでサーバーコードを見てみると、114行目で、MQL5クライアントがメッセージを完全に受信したことを判断できるように、必要な文字を追加しているのが分かります。これをサーバー側でおこなわなければ、MQL5で実装されたミニチャットは正しく動作しません。
自分自身でサーバーを実装する場合には、この点を必ず保証してください。サーバーが同じ文字を必ず追加する必要があります。順序は問いませんが、メッセージの末尾に含まれていなければなりません。
そして、ここからが面白い部分です。先ほど、クライアントがサーバーを終了させることができると述べましたが、これはどのように実現されているのでしょうか。答えは簡単です。95行目を見てください。ここでは、メッセージの先頭にある文字が、サーバーに実行させるコマンドであることを示すように定義されています。このテストが成功すると、そのコマンドが解析され、投稿されたメッセージは他の接続クライアントへ再送信されません。そして99行目で、そのコマンドのひとつをチェックします。この判定は大文字小文字を区別するため、サーバーが期待する正確な形式でコマンドが送られなければなりません。それが満たされると、101行目でサーバーに対して動作を終了し、すべてのオープン中の接続を閉じるよう指示します。
ご覧のとおり、すべては非常にシンプルです。ただし、これはあくまで教育目的のコードであることを忘れないでください。なぜなら、どのクライアントからでもサーバーにコマンドを送信できてしまうからです。実際のサーバーでは、これを防ぐために認証レベルの導入や、その他のセキュリティ対策が必要になりますが、ここではそのような対策は不要です。
最終的な考察
本記事では、外部プログラミングをMQL5と組み合わせてミニチャットを作成する方法の解説を締めくくります。その目的はあくまで教育的なものですが、改良や拡張をおこなうことで、友人同士で使ったり、よりプロフェッショナルな仕組みの基礎として利用したりすることも可能です。この知識をぜひ有効に活用してください。添付ファイルには、すでにコンパイル済みで、そのまま使用できるミニチャットのMQL5コードが含まれています。必要なのはサーバーを作成することだけです。ただし、これにはコンピュータ上でポートを開放する作業が伴うため、その実装は読者の皆さん自身に任せたいと思います。動画の中でミニチャットと一緒に使用しているプログラムはPuTTYです。
| ファイル | 説明 |
|---|---|
| Experts\Expert Advisor.mq5 | Chart TradeとEAの連携を示す(Mouse Studyが必要) |
| Indicators\Chart Trade.mq5 | 送信する注文を設定するウィンドウを作成する(Mouse Studyが必要) |
| Indicators\Market Replay.mq5 | リプレイ/シミュレーターサービスと対話するためのコントロールを作成する(Mouse Studyが必要) |
| Indicators\Mouse Study.mq5 | グラフィカルコントロールとユーザー間のインタラクションを実現する(リプレイ/シミュレーターおよび実取引の両方で必須) |
| Servicios\Market Replay.mq5 | マーケットリプレイおよびシミュレーションサービスを生成し、維持する(システム全体のメインファイル) |
| VS C++ Server.cpp | C++でソケットサーバーを作成および管理する(ミニチャットバージョン) |
| Python code Server.py | MetaTrader 5とExcel間の通信用のPythonソケットを作成および維持する |
| ScriptsCheckSocket.mq5 | 外部ソケットとの接続を確認する |
| Indicators\Mini Chat.mq5 | インジケーターとしてミニチャットを実装する(サーバーが必要) |
| Experts\Mini Chat.mq5 | ミニチャットをEAとして実装する(サーバーが必要) |
MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/12673
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
血液型遺伝最適化(BIO)
初級から中級まで:イベント(I)
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
IBMの量子コンピュータを使ってすべての価格変動パターンを解析する
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索