А давайте сейчас быстренько набрасаем чатик. Задействуем pub/sub механизмы: примерно так работают всякие копировщики, сигнальные сервисы и мониторинги.
Постановка задачи
делаем микро-чат:
- это будет индикатор
- с единственным (опциональным) параметром «NAME» - имя с общем чате
- в поле коментариев отображается история чата (кто что говорил)
- интерфейс пользователя делаем на нативном MQL: поле для ввода текста и кнопка «send»
- при нажатии на кнопку send отправляем введённый текст всем другим пользователям
Подоснова
для сетевого взаимодействия возьмём протокол MQTT https://mqtt.org/ , и его клиентскую часть https://chiselapp.com/user/schelte/repository/mqtt/wiki?name=Tcl+MQTT+client (там есть ещё и брокер, но пока не про него). Этот пакет уже включен в ATcl
В двух словах про MQTT - клиенты могут публиковать произвольные данные в темы организованные иерархическим образом. И могут подписываться на получение данных из этой иерархии. Получается быстрая эффективная связь один-ко-многим. Это действительно очень быстро, это используется в IoT например для передачи показаний датчиков.
в качестве сервера (пока, для тестов) используем тестовый сервер test.mosquitto.org см. https://www.mosquitto.org/
будем использовать корневой топик atcl/8891/talk - это произвольное имя, просто чтобы не запутался случайно с другими темами тестового сервера. Все публикации будут с таким «корнем»
Реализация
Всю сетевую часть делаем на tcl и предоставляем на уровень MQL несколько функций:
- start - начать работу
- send имя текст - передать текст от заданного имени. (в терминах mqtt - записать «текст» в тему «имя»)
- stop - завершить работу
- receive - получить накопившиеся сообщения. MQL будет просто периодично её дёргать и смотреть что получено
процедура receive будет возвращать список сообщений, первым полем будет код:
- 0 - соединение потеряно
- 1 - соединение установлено
- 2 - получено новое сообщение
- 3 - получено сообщение ранее сохранённое на сервере
На Tcl 60 строчек всё это делают :
package require mqtt set SERVER "test.mosquitto.org" ;# сервер для тестов set MSGS {} ;# сообщения для MQL set MQTT {} ;# дескриптор mqtt set root "atcl/8891/talk" ;# топик set connected 0 ;# соединён/нет proc receive {} { set ret $::MSGS set ::MSGS {} set ret } proc start {} { set ::MQTT [ mqtt new ] $::MQTT connect "" $::SERVER $::MQTT subscribe "\$SYS/local/connection" [ list OnConnection ] $::MQTT subscribe "$::root/#" [ list OnMessage ] } proc stop {} { if { $::MQTT != {} } { catch { $::MQTT destroy } catch {rename $::MQTT {} } set ::MQTT {} set connected "" } } proc send { who args } { if { !$::connected } { return } # payloadformat указывает что передаём текст в utf-8 $::MQTT publish -properties {PayloadFormatIndicator 1} $::root/$who [ join $args " " ] 0 1 } proc OnConnection { topic message retain args } { set state [ dict get $message state ] if { $state == "connected" } { set ::connected 1 ToMQL 1 } elseif { $state == "disconnected" } { set ::connected 0 ToMQL 0 } } proc OnMessage { topic message retain args } { set path [ split $topic "/" ] set who [ lindex $path end ] if { $retain } { ToMQL 3 $who $message } else { ToMQL 2 $who $message } } proc ToMQL { code args } { lappend ::MSGS [ list $code {*}$args ] }
На MQL втрое больше :
#property indicator_chart_window #resource "talk.tcl" as string talk_tcl; input string _NAME=""; string NAME; #include <ATcl/ATcl.mqh> ATcl *tcl=NULL; string TextInput=".talk.Input"; string ButtonSend=".talk.Send"; string Prepare(string name) { string out=""; int len=StringLen(name); for(int pos=0;pos<len;pos++) { ushort ch=StringGetCharacter(name,pos); if (ch=='/' || ch=='*' || ch=='+' || ch=='\\' || ch=='#') { continue; } out=out+ShortToString(ch); } StringTrimLeft(out);StringTrimRight(out); return out; } int OnInit() { // разборка с аргументами // NAME - не пустая строка, без спец.символов NAME=Prepare(_NAME); if (NAME=="") { NAME=Prepare(AccountInfoString(ACCOUNT_NAME)); if (NAME=="") { Alert("NAME wrong"); return INIT_FAILED; } } // инициализация библиотеки, создание интерпретатора if (ATcl_OnInit()!=INIT_SUCCEEDED) { return INIT_FAILED; } tcl=new ATcl(); if (tcl==NULL || tcl.OnInit()!=INIT_SUCCEEDED) { return INIT_FAILED; } // выполняем скрипт из ресурса if (tcl.Eval(talk_tcl)!=TCL_OK) { PrintFormat("script error: %s",tcl.StringResult()); return INIT_FAILED; } // запускаем говорилку if (tcl.Call(tcl.Obj("start"))!=TCL_OK) { Alert("start failed "+tcl.StringResult()); return INIT_FAILED; } EventSetMillisecondTimer(30); ObjectCreate(0,TextInput,OBJ_EDIT,0,0,0); ObjectSetInteger(0,TextInput,OBJPROP_XDISTANCE,300); ObjectSetInteger(0,TextInput,OBJPROP_YDISTANCE,2); ObjectSetInteger(0,TextInput,OBJPROP_XSIZE,400); ObjectCreate(0,ButtonSend,OBJ_EDIT,0,0,0); ObjectSetInteger(0,ButtonSend,OBJPROP_XDISTANCE,700); ObjectSetInteger(0,ButtonSend,OBJPROP_YDISTANCE,2); ObjectSetInteger(0,ButtonSend,OBJPROP_XSIZE,60); ObjectSetString(0,ButtonSend,OBJPROP_TEXT,"Send"); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if (tcl!=NULL) { tcl.Call(tcl.Obj("stop")); tcl.OnDeinit(reason); delete tcl; tcl=NULL; } EventKillTimer(); ObjectsDeleteAll(0,".talk."); ChartRedraw(0); } void OnTimer() { tcl.Update(); // получаем список сообщений if (tcl.Call(tcl.Obj("receive"))!=TCL_OK) { Alert("receive failed"); ExpertRemove(); return; } Tcl_Obj list=tcl.Result(); if (list==0) return; // разбираем все сообщения tcl.Ref(list); int len=tcl.Count(list); for(int pos=0;pos<len;pos++) { Tcl_Obj msg=tcl.Index(list,pos); if (msg==0) continue; tcl.Ref(msg); long code=tcl.Long(msg,0); switch ((int)code) { case 0: // disconnect AppendToComment("** disconnect **"); break; case 1: // connect AppendToComment("** connect **"); break; case 2: // message { string who=tcl.String(msg,1); string text=tcl.String(msg,2); AppendToComment(StringFormat("%s : %s",who,text)); } break; case 3: // retained message { string who=tcl.String(msg,1); string text=tcl.String(msg,2); AppendToComment(StringFormat("( %s : %s )",who,text)); } break; } tcl.Unref(msg); } tcl.Unref(list); } int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { tcl.Update(); return(rates_total); } void OnChartEvent(const int id,const long& lparam, const double& dparam, const string& sparam) { if (id==CHARTEVENT_OBJECT_ENDEDIT && sparam==TextInput) { OnInput(); ChartRedraw(0); } else if (id==CHARTEVENT_OBJECT_CLICK && sparam==ButtonSend) { OnInput(); ObjectSetInteger(0,ButtonSend,OBJPROP_STATE,0); ChartRedraw(0); } } void OnInput() { string text=ObjectGetString(0,TextInput,OBJPROP_TEXT); StringTrimLeft(text);StringTrimRight(text); if (text=="") return; tcl.Call(tcl.Obj("send"),tcl.Obj(NAME),tcl.Obj(text)); ObjectSetString(0,TextInput,OBJPROP_TEXT,""); } void AppendToComment(string text) { string comment=ChartGetString(0,CHART_COMMENT); string lines[]; StringSplit(comment,'\n',lines); if (ArraySize(lines)>20) { ArrayRemove(lines,ArraySize(lines)-20); int total=ArraySize(lines); comment=lines[0]; for(int pos=1;pos<total;pos++) { comment=comment+"\n"+lines[pos]; } } comment=comment+"\n"+text; Comment(comment); }
Результат
И можем в нём даже пообщаться
Вот так вот за часик с перекуром, сделали конкурента телеграм и основу сервиса сигналов и копирования сделок
Всем чмоки в этом чате :-)
Скачать библиотеку ATcl отсюда https://uploadfiles.in/6q9 или https://dfiles.eu/files/3sigiedi1 или https://www.filefactory.com/file/nmqp3op9f9q/atclsetup_1.01.exe
а также со страницы загрузки эскизного сайта http://atcl.unaux.com/download/
или следуя инструкциям, из репозитария проекта https://chiselapp.com/user/nektomk/repository/atcl-lib/home
Статьи на mql5.com про использование ATcl:
- Делаем GUI , часть 1 https://www.mql5.com/ru/blogs/post/751482 , часть 2 https://www.mql5.com/ru/blogs/post/751489, часть 3 https://www.mql5.com/ru/blogs/post/751499
- Делает отчёт в Word : https://www.mql5.com/ru/blogs/post/751536
- Базы данных (обзор) : https://www.mql5.com/ru/blogs/post/751548
- ATcl & Excel : https://www.mql5.com/ru/blogs/post/751580