php mql web开发,自己动手开发多线程异步 MQL5 WebRequest

2.6. 管理器 EA 和助手 EA

我们终于到了”服务器“部分,因为基本机制已经在头文件中实现,管理器和助手的代码就没有想象得那样复杂了,

您也许记得,我们只有一个EA,它既以管理器工作,也可以作为助手 (multiweb.mq5 文件)。作为客户,我们包含头文件并声明输入参数:

sinput uint WebRequestPoolSize = 3;

sinput ulong ManagerChartID = 0;

#include

WebRequestPoolSize 是管理器应当创建辅助窗口的数量,再在其中运行助手,

ManagerChartID 是管理器窗口 ID,这个参数只有在助手中可以使用,是在助手从代码中自动运行的时候要填充到管理器中的。当运行管理器时人工填写 ManagerChartID 会被认为出错。

算法是基于两个全局变量构建的:

bool manager;

WebWorkersPool pool;

'manager' 本地标志指示了当前EA实例的角色,'pool' 变量是用于到来任务的处理器对象的数组。WebWorkersPool 是通过上面所描述的 ServerWebWorker 类来分类的。数组没有进一步初始化,因为它是根据角色填充的。

第一个运行的实例 (在 OnInit 中定义) 得到的是管理器角色。

const string GVTEMP = 'WRP_GV_TEMP';

int OnInit()

{

manager = false;

if(!GlobalVariableCheck(GVTEMP))

{

// 当开始启动 multiweb 的第一个实例是,它被当成管理器

// 全局变量是管理器存在的标志

if(!GlobalVariableTemp(GVTEMP))

{

FAILED(GlobalVariableTemp);

return INIT_FAILED;

}

manager = true;

GlobalVariableSet(GVTEMP, 1);

Print('WebRequest Pool Manager started in ', ChartID());

}

else

{

// 所有随后的 multiweb 实例都是工作单元/助手

Print('WebRequest Worker started in ', ChartID(), '; manager in ', ManagerChartID);

}

// 使用计时器来延迟工作单元的初始化

EventSetTimer(1);

return INIT_SUCCEEDED;

}

EA 检查终端中是否有特定的全局变量,如果没有,EA 会把自身赋为管理器,并创建这样的一个全局变量。如果变量已经存在,那么这就是有管理器,所以这个实例就变成一个助手。请注意,全局变量是临时的,也就是说在终端重新启动的时候它不会被保存,但是如果管理器保留在任何图表上,它会再次创建这个变量。

计时器设为1秒,因为辅助图表的初始化会花好几秒,在 OnInit 中做这些不是最好的方案。在计时器事件处理函数中填充池:

void OnTimer()

{

EventKillTimer();

if(manager)

{

if(!instantiateWorkers())

{

Alert('Workers not initialized');

}

else

{

Comment('WebRequest Pool Manager ', ChartID(), '\nWorkers available: ', pool.available());

}

}

else // 工作单元

{

// 这是用于资源的宿主,保存回应的头部和数据

pool << new ServerWebWorker(ChartID(), 'WRR_');

}

}

如果是助手角色,就简单把另一个 ServerWebWorker 处理器对象加到数组中管理器的情况更加复杂,要在独立的 instantiateWorkers 函数中处理,让我们看看它。

bool instantiateWorkers()

{

MqlParam Params[4];

const string path = MQLInfoString(MQL_PROGRAM_PATH);

const string experts = '\\MQL5\\';

const int pos = StringFind(path, experts);

// 再次启动自身 (以助手EA的角色)

Params[0].string_value = StringSubstr(path, pos StringLen(experts));

Params[1].type = TYPE_UINT;

Params[1].integer_value = 1; // 新的助手EA实例中有一个工作单元,用于返回结果到管理器或客户

Params[2].type = TYPE_LONG;

Params[2].integer_value = ChartID(); // 这个图表是管理器

Params[3].type = TYPE_UINT;

Params[3].integer_value = MessageBroadcast; // 使用相同的自定义事件基础编号

for(uint i = 0; i < WebRequestPoolSize; i)

{

long chart = ChartOpen(_Symbol, _Period);

if(chart == 0)

{

FAILED(ChartOpen);

return false;

}

if(!EXPERT::Run(chart, Params))

{

FAILED(EXPERT::Run);

return false;

}

pool << new ServerWebWorker(chart);

}

return true;

}

这个函数使用了第三方的 Expert 开发库,是由我们的老朋友 - MQL5 社区成员 fxsaber 开发的, 所以在源代码的开头加入了对应的头文件。

#include

Expert 开发库允许您动态生成 tpl 模板和指定的 EA 参数,并且把它们应用到图表上,就能够载入 EA,在我们的实例中,所有助手EA的参数都是相同的,所以它们的列表在创建指定数量的窗口之前就生成了,

参数 0 指定了执行EA文件的路径,也就是它自己。参数 1 是 WebRequestPoolSize,它在每个助手中都等于1. 我已经提过,处理器对象在助手中只是用于保存 HTTP 请求结果的资源的,每个助手都通过阻塞式的 WebRequest 处理请求,也就是最多使用一个处理器对象。参数 2 — ManagerChartID 管理器窗口 ID. 参数 3 — 消息代码的基础数值 (MessageBroadcast 参数是来自 multiweb.mqh).

另外,在循环中使用了 ChartOpen 来创建了空白的图表,并且使用 EXPERT::Run (chart, Params) 来在其中运行 EA。ServerWebWorker(chart) 处理器对象在每个新窗口中创建,并加入池中。在管理器中,处理器对象就像助手窗口 ID 的连接和它们的状态,因为 HTTP 请求不是在管理器自身中进行的,而不会为它们创建资源。

来临的任务是根据用于在 OnChartEvent 中的事件来处理的。

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)

{

if(MSG(id) == MSG_DISCOVER) // 在新的客户图表中初始化一个工作单元EA并绑定到管理器

{

if(manager && (lparam != 0))

{

// 只有管理器使用它的图表ID回应,lparam 是客户图表 ID

EventChartCustom(lparam, TO_MSG(MSG_HELLO), ChartID(), pool.available(), NULL);

}

}

else

if(MSG(id) == MSG_WEB) // 已经有了请求 web 下载的客户端

{

if(lparam != 0)

{

if(manager)

{

// 管理器把工作分发到空闲工作单元

// lparam 是客户图表ID,而 sparam 是客户资源

if(!transfer(lparam, sparam))

{

EventChartCustom(lparam, TO_MSG(MSG_ERROR), ERROR_NO_IDLE_WORKER, 0.0, sparam);

}

}

else

{

// 工作单元实际处理 web 请求

startWebRequest(lparam, sparam);

}

}

}

else

if(MSG(id) == MSG_DONE) // 一个根据在 lparam 中的图表 ID 识别到的工作单元完成了工作

{

WebWorker *worker = pool.findWorker(lparam);

if(worker != NULL)

{

// 我们这里是在管理器中,并且池中只保存工作单元而没有资源,

// 所以这个 release 只是用于清除繁忙状态

worker.release();

}

}

}

首先,为了回应来自客户端带有 lparam ID 的 MSG_DISCOVER 消息,管理器要返回含有它窗口 ID 的 MSG_HELLO 消息,

根据接收到的 MSG_WEB, lparam 应当包含发送请求的客户的窗口 ID,而 sparam 应当包含打包了请求参数的资源的名称。作为管理器工作,代码会把包含这些参数的任务传递给一个空闲助手,调用的是 'transfer' 函数 (下面会描述) 并把选中对象的状态设为 'busy(繁忙)'。如果没有空闲的助手,就向客户发送 MSG_ERROR 事件,代码为 ERROR_NO_IDLE_WORKER。助手在 startWebRequest 函数中执行 HTTP 请求。

当上传了所需的文档时,会从助手到管理器发送 MSG_DONE 消息,管理器从中根据 lparam 中的助手ID来寻找对应的对象,并通过调用 ‘release' 方法修改它的“busy'状态。已经说过,助手会把运行的结果直接发送给客户。

完整的代码中也包含了 MSG_DEINIT 事件,它与 OnDeinit 的处理相关。思路是,助手在管理器删除时被通知,然后自己退出并关闭它们的窗口,而再通知管理器助手已经被删除,并从管理器池中删除它。我相信,您可以自己了解其中的机制。

'transfer' 函数搜索空闲对象,并调用它的 'transfer' 方法 (上面讨论过)。

bool transfer(const long returnChartID, const string resname)

{

ServerWebWorker *worker = pool.getIdleWorker();

if(worker == NULL)

{

return false;

}

return worker.transfer(resname, returnChartID);

}

startWebRequest 函数描述如下:

void startWebRequest(const long returnChartID, const string resname)

{

const RESOURCEDATA resource(resname);

ResourceMediator mediator(&resource);

string method, url, headers;

int timeout;

uchar body[];

mediator.unpackRequest(method, url, headers, timeout, body);

char result[];

string result_headers;

int code = WebRequest(method, url, headers, timeout, body, result, result_headers);

if(code != -1)

{

// 使用结果创建资源,通过自定义事件传回客户端

((ServerWebWorker *)pool[0]).receive(resname, result, result_headers);

// 首先,向客户发送 MSG_DONE,包括结果资源。

EventChartCustom(returnChartID, TO_MSG(MSG_DONE), ChartID(), (double)code, pool[0].getFullName());

// 第二, 发送 MSG_DONE 到管理器,把对应工作单元设为空闲状态

EventChartCustom(ManagerChartID, TO_MSG(MSG_DONE), ChartID(), (double)code, NULL);

}

else

{

// 错误代码在 dparam 中

EventChartCustom(returnChartID, TO_MSG(MSG_ERROR), ERROR_MQL_WEB_REQUEST, (double)GetLastError(), resname);

EventChartCustom(ManagerChartID, TO_MSG(MSG_DONE), ChartID(), (double)GetLastError(), NULL);

}

}

通过使用 ResourceMediator, 这个函数解包取得请求的参数并调用标准的 MQL WebRequest 函数,如果函数执行没有出现 MQL 错误,就把结果发送给客户。为此,使用 ’receive' 方法把它们打包到本地资源中,而它的名称与 MSG_DONE 消息在 EventChartCustom 函数的 sparam 参数中传递。请注意,HTTP 错误 (例如,无效页面 404 或者 web 服务器错误 501) 也会在这里出现 — 客户可以在 dparam 参数中收到 HTTP 代码,在资源中收到回应的 HTTP 头部,这可以让我们进一步进行分析。

如果 WebRequest 调用以 MQL 错误结束, 客户会收到 MSG_ERROR 消息和 ERROR_MQL_WEB_REQUEST 代码, 而 GetLastError 的结果放在 dparam 中。因为这种情况下本地资源没有填充,来源资源的名称会直接放在 sparam 参数中,这样处理对象的某个实例还是能在客户端识别。

151756917_4_20190111030649521.png

图 4. 用于异步和并行调用 WebRequest 的 multiweb 开发库类图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值