diff --git a/Include/Zmq/AtomicCounter.mqh b/Include/Zmq/AtomicCounter.mqh index df57808..4cf58cc 100644 --- a/Include/Zmq/AtomicCounter.mqh +++ b/Include/Zmq/AtomicCounter.mqh @@ -3,8 +3,6 @@ //| Copyright 2016, Li Ding | //| dingmaotu@hotmail.com | //+------------------------------------------------------------------+ -#property copyright "Copyright 2016, Li Ding" -#property link "dingmaotu@hotmail.com" #property strict #include "Common.mqh" diff --git a/Include/Zmq/Common.mqh b/Include/Zmq/Common.mqh index 650ad46..7b99724 100644 --- a/Include/Zmq/Common.mqh +++ b/Include/Zmq/Common.mqh @@ -1,10 +1,10 @@ //+------------------------------------------------------------------+ //| Common.mqh | +//| This file is part of mql4-lib project (Lang/Native.mqh): | +//| (github.com/dingmaotu/mql4-lib) | //| Copyright 2016, Li Ding | //| dingmaotu@hotmail.com | //+------------------------------------------------------------------+ -#property copyright "Copyright 2016, Li Ding" -#property link "dingmaotu@hotmail.com" #property strict #include "Errno.mqh" diff --git a/Include/Zmq/Context.mqh b/Include/Zmq/Context.mqh index 587d903..51d1262 100644 --- a/Include/Zmq/Context.mqh +++ b/Include/Zmq/Context.mqh @@ -6,6 +6,7 @@ #include "Common.mqh" #include "SocketOptions.mqh" +#include "GlobalHandle.mqh" //--- Context options #define ZMQ_IO_THREADS 1 @@ -30,19 +31,34 @@ int zmq_ctx_get(intptr_t context,int option); #import //+------------------------------------------------------------------+ //| Wraps a 0MZ context | +//| | +//| Note on context creation: | +//| In the official guide: | +//| You should create and use exactly one context in your process. | +//| Technically, the context is the container for all sockets in a | +//| single process, and acts as the transport for inproc sockets, | +//| which are the fastest way to connect threads in one process. | +//| If at runtime a process has two contexts, these are like | +//| separate ZeroMQ instances. | +//| In metatrader Terminal, every Script and Expert Advsior has its | +//| own thread, but they all share a process, that is the Terminal. | +//| So it is advised to use a single global context on all your MQL | +//| programs. The `shared` parameter is used for sychronization of | +//| context creation and destruction. It is better named globally, | +//| and in a manner not easily recognized by humans, for example: | +//| "__3kewducdxhkd__" | //+------------------------------------------------------------------+ -class Context +class Context: public GlobalHandle { -private: - intptr_t m_ref; protected: int get(int option) {return zmq_ctx_get(m_ref,option);} bool set(int option,int optval) {return 0==zmq_ctx_set(m_ref,option,optval);} + + intptr_t create() override {return zmq_ctx_new();} + void destroy(intptr_t handle) override {if(0!=zmq_ctx_term(handle)) {Debug("failed to terminate context");}} public: - Context() {m_ref=zmq_ctx_new();} - ~Context() {if(0!=zmq_ctx_term(m_ref)){Debug("failed to terminate context");}} - // for better cooperation between objects - intptr_t ref() const {return m_ref;} + Context(string shared=NULL):GlobalHandle(shared) {} + bool shutdown() {return 0==zmq_ctx_shutdown(m_ref);} int getIoThreads() {return get(ZMQ_IO_THREADS);} diff --git a/Include/Zmq/GlobalHandle.mqh b/Include/Zmq/GlobalHandle.mqh new file mode 100644 index 0000000..73c3065 --- /dev/null +++ b/Include/Zmq/GlobalHandle.mqh @@ -0,0 +1,125 @@ +//+------------------------------------------------------------------+ +//| GlobalHandle.mqh | +//| This file is part of mql4-lib project (Lang/GlobalVariable.mqh): | +//| (github.com/dingmaotu/mql4-lib) | +//| Copyright 2017, Li Ding | +//| dingmaotu@hotmail.com | +//+------------------------------------------------------------------+ +#property strict +//+------------------------------------------------------------------+ +//| Wraps global variable functions | +//+------------------------------------------------------------------+ +class GlobalVariable + { +public: + static int total() {return GlobalVariablesTotal();} + static string name(int index) {return GlobalVariableName(index);} + static void flush() {GlobalVariablesFlush();} + + static bool exists(string name) {return GlobalVariableCheck(name);} + static datetime lastAccess(string name) {return GlobalVariableTime(name);} + + static bool makeTemp(string name) {return GlobalVariableTemp(name);} + static double get(string name) {return GlobalVariableGet(name);} + static bool get(string name,double &value) {return GlobalVariableGet(name,value);} + static datetime set(string name,double value) {return GlobalVariableSet(name,value);} + static bool setOn(string name,double value,double check) {return GlobalVariableSetOnCondition(name,value,check);} + + static bool remove(string name) {return GlobalVariableDel(name);} + static bool removeAll(string prefix=NULL,datetime before=0) {return GlobalVariablesDeleteAll(prefix,before);} + }; +//+------------------------------------------------------------------+ +//| CriticalSection object for making atomic operations | +//| | +//| An exmaple of creating a global context (the creation and destroy| +//| are both enclosed between the SAME critical section): | +//| | +//| enter() | +//| if(refcount==0) create context | +//| else refcontext | +//| increase refcount | +//| leave() | +//| | +//| enter() | +//| decrease refcount | +//| if(refcount==0) context destroy | +//| leave() | +//+------------------------------------------------------------------+ +class CriticalSection + { +private: + const string m_name; +public: + CriticalSection(string name):m_name(name){} + + bool isValid() const {return m_name!=NULL;} + string getName() const {return m_name;} + + void enter() { while(!GlobalVariable::makeTemp(m_name))Sleep(100); } + bool tryEnter() { return GlobalVariable::makeTemp(m_name); } + void leave() { GlobalVariable::remove(m_name);} + }; +//+------------------------------------------------------------------+ +//| A reference counted global pointer (or handle) | +//+------------------------------------------------------------------+ +template +class GlobalHandle + { +private: + CriticalSection m_cs; + string m_refName; + string m_counterName; +protected: + T m_ref; +public: + GlobalHandle(string sharedKey=NULL):m_cs(sharedKey) + { + m_refName=m_cs.getName()+"_Ref"; + m_counterName=m_cs.getName()+"_Count"; + if(!m_cs.isValid()) m_ref=create(); + else + { + m_cs.enter(); + if(!GlobalVariable::exists(m_counterName)) + { + GlobalVariable::makeTemp(m_counterName); + GlobalVariable::set(m_counterName,0); + } + if(long(GlobalVariable::get(m_counterName))==0) + { + m_ref=create(); + if(!GlobalVariable::exists(m_refName)) + { + GlobalVariable::makeTemp(m_refName); + GlobalVariable::set(m_refName,m_ref); + } + } + else + { + m_ref=(T)(GlobalVariable::get(m_refName)); + } + GlobalVariable::set(m_counterName,GlobalVariable::get(m_counterName)+1); + m_cs.leave(); + } + } + ~GlobalHandle() + { + if(!m_cs.isValid()) {destroy(m_ref); return;} + m_cs.enter(); + GlobalVariable::set(m_counterName,GlobalVariable::get(m_counterName)-1); + if(long(GlobalVariable::get(m_counterName))==0) + { + destroy(m_ref); + GlobalVariable::remove(m_refName); + GlobalVariable::remove(m_counterName); + } + m_cs.leave(); + } + + T ref() const {return m_ref;} + +protected: + virtual T create()=NULL; + virtual void destroy(T handle)=NULL; + }; +//+------------------------------------------------------------------+ diff --git a/Include/Zmq/Socket.mqh b/Include/Zmq/Socket.mqh index 014daa5..ba90c4e 100644 --- a/Include/Zmq/Socket.mqh +++ b/Include/Zmq/Socket.mqh @@ -3,8 +3,6 @@ //| Copyright 2016, Li Ding | //| dingmaotu@hotmail.com | //+------------------------------------------------------------------+ -#property copyright "Copyright 2016, Li Ding" -#property link "dingmaotu@hotmail.com" #property strict #include "Common.mqh" diff --git a/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldClient.mq4 b/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldClient.mq4 index 83fc118..5788c10 100644 Binary files a/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldClient.mq4 and b/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldClient.mq4 differ diff --git a/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldServer.mq4 b/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldServer.mq4 index 4906578..698f982 100644 --- a/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldServer.mq4 +++ b/Scripts/ZeroMQGuideExamples/Chapter1/HelloWorldServer.mq4 @@ -15,12 +15,12 @@ //+------------------------------------------------------------------+ void OnStart() { - Context context; + Context context("helloworld"); Socket socket(context,ZMQ_REP); socket.bind("tcp://*:5555"); - while(true) + while(!IsStopped()) { ZmqMsg request; diff --git a/Scripts/ZeroMQGuideExamples/Chapter1/VersionReporting.mq4 b/Scripts/ZeroMQGuideExamples/Chapter1/VersionReporting.mq4 new file mode 100644 index 0000000..5648a93 --- /dev/null +++ b/Scripts/ZeroMQGuideExamples/Chapter1/VersionReporting.mq4 @@ -0,0 +1,19 @@ +//+------------------------------------------------------------------+ +//| VersionReporting.mq4.mq4 | +//| Copyright 2016, Li Ding | +//| dingmaotu@hotmail.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2016, Li Ding" +#property link "dingmaotu@hotmail.com" +#property version "1.00" +#property strict + +#include +//+------------------------------------------------------------------+ +//| Report 0MQ version | +//+------------------------------------------------------------------+ +void OnStart() + { + Print(Zmq::getVersion()); + } +//+------------------------------------------------------------------+ diff --git a/Scripts/ZeroMQGuideExamples/Chapter1/WeatherUpdateClient.mq4 b/Scripts/ZeroMQGuideExamples/Chapter1/WeatherUpdateClient.mq4 new file mode 100644 index 0000000..ea127c0 Binary files /dev/null and b/Scripts/ZeroMQGuideExamples/Chapter1/WeatherUpdateClient.mq4 differ diff --git a/Scripts/ZeroMQGuideExamples/Chapter1/WeatherUpdateServer.mq4 b/Scripts/ZeroMQGuideExamples/Chapter1/WeatherUpdateServer.mq4 new file mode 100644 index 0000000..51ce986 --- /dev/null +++ b/Scripts/ZeroMQGuideExamples/Chapter1/WeatherUpdateServer.mq4 @@ -0,0 +1,54 @@ +//+------------------------------------------------------------------+ +//| WeatherUpdateServer.mq4.mq4 | +//| Copyright 2016, Li Ding | +//| dingmaotu@hotmail.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2016, Li Ding" +#property link "dingmaotu@hotmail.com" +#property version "1.00" +#property strict + +#include + +#define within(num) (int) ((float) num * MathRand() / (32767 + 1.0)) +//+------------------------------------------------------------------+ +//| Weather update server in MQL | +//| Binds PUB socket to tcp://*:5556 | +//| Publishes random weather updates | +//+------------------------------------------------------------------+ +void OnStart() + { +//--- Prepare our context and publisher + Context context; + Socket publisher(context,ZMQ_PUB); + publisher.bind("tcp://*:5556"); + + long messages_sent=0; +//--- Initialize random number generator + MathSrand(GetTickCount()); + while(!IsStopped()) + { + int zipcode,temperature,relhumidity; + + // Get values that will fool the boss + + // MetaTrader Note: + // if RAND_MAX < 100000, which is the case for MetaTrader, + // you may never get the required value + // So 30000 might be a good alternative + zipcode=within(30000); + temperature=within(215) - 80; + relhumidity=within(50) + 10; + + // Send message to all subscribers + ZmqMsg message(StringFormat("%05d %d %d",zipcode,temperature,relhumidity)); + publisher.send(message); + messages_sent++; + + if(messages_sent%1000000==0) + { + PrintFormat("Sent %dM messages now.",messages_sent/1000000); + } + } + } +//+------------------------------------------------------------------+