Sở hữu ngay máy chủ VPS Robot Forex khi giao dịch tại HotForex

Chào mừng các bạn đến với chuỗi bài về lập trình Robot Forex!

Việc thực hiện các thuật toán giao dịch thường dựa trên yêu cầu phân tích dữ liệu từ nhiều nguồn bên ngoài khác nhau, bao gồm cả Internet. MQL4/MQL5 cung cấp chức năng WebRequest để gửi các yêu cầu HTTP đến “thế giới bên ngoài”, nhưng thật không may, nó có một nhược điểm đáng chú ý. Hàm hoạt động đồng bộ, nghĩa là nó sẽ chặn hoạt động EA trong toàn bộ thời gian thực hiện yêu cầu (tương tự với Meta Trader 4). Đối với mỗi EA, MetaTrader sẽ phân bổ một luồng process duy nhất để thực hiện tuần tự các lệnh gọi hàm API hiện có trong mã, cũng như các trình xử lý sự kiện đến (như tick, độ sâu thay đổi thị trường trong BookEvent, bộ đếm thời gian, hoạt động giao dịch, sự kiện biểu đồ, v.v. ). Mỗi lần chỉ có một đoạn mã được thực thi, trong khi tất cả các “tác vụ” còn lại chờ đến lượt trong hàng đợi, cho đến khi đoạn hiện tại trả lại quyền điều khiển cho kernel.

Mục lục:

Ví dụ: nếu một EA nên xử lý các thay đổi giá mới nhận từ thị trường trong thời gian thực và kiểm tra định kỳ tin tức kinh tế trên một số trang web bên ngoài thì không thể thực hiện cả hai yêu cầu mà không can thiệp lẫn nhau. Ngay sau khi WebRequest được thực thi trong mã, EA vẫn “đóng băng” trên chuỗi lệnh gọi hàm, trong khi các sự kiện OnTick mới được bỏ qua. Ngay cả với khả năng đọc thị trường bằng cách sử dụng chức năng CopyTicks thì cũng sẽ bị bỏ qua. Vì vậy dẫn đến thời điểm đưa ra quyết định giao dịch của EA có thể bị bỏ lỡ. Dưới đây là cách minh họa tình huống này bằng sơ đồ khối trình tự:

Về vấn đề này, sẽ tốt hơn nếu chúng ta tạo một công cụ để thực thi song song và không chặn đồng bộ các yêu cầu HTTP, từ đó một loại WebRequestAsync được phát triển, chỉ có trên Meta Trader 5.

Rõ ràng, để giải quyết điều này, cần phải có cách đồng bộ song song với các request http. Cách dễ nhất để làm điều này trong MetaTrader là chạy các EA bổ sung mà bạn có thể gửi các yêu cầu HTTP bổ sung song song. Ngoài ra, bạn có thể gọi WebRequest ở đó và nhận được kết quả sau một thời gian. Mặc dù yêu cầu http vẫn đang được xử lý trong một assistant EA, nhưng EA chính vẫn avaiable để giao dịch và cho ra các hành động nhanh chóng và chuẩn xác trên chart. Sơ đồ trình tự UML cho trường hợp của chúng ta như sau:

Sơ đồ trình ủy quyền xử lý sự kiện không đồng bộ cho các luồng khác nhau

Ý tưởng

Như bạn đã biết, mỗi EA nên hoạt động trên một biểu đồ riêng trong MetaTrader. Do đó, việc tạo ra các assistant EA đòi hỏi tạo các biểu đồ dành riêng cho chúng. Làm thủ công là bất tiện. Do đó, việc ủy ​​thác tất cả các hành động này cho một quản lý đặc biệt – một EA Manager sẽ quản lý nhóm các biểu đồ và assistant EA, đồng thời cung cấp một đầu vào duy nhất để đăng ký các yêu cầu từ thiết bị đầu cuối Meta Trader. Theo một nghĩa nào đó, kiến ​​trúc này có thể được gọi là kiến ​​trúc 3 cấp tương tự như kiến ​​trúc Server-Client, nơi trình quản lý manager EA hoạt động như Server và các assistant EA hoạt động như các Client

Kiến trúc thư viện multiweb: mã MQL của client – máy chủ Manager EA <—> assistant EA

Nhưng để đơn giản hóa, Manager EA và assistant EA có thể được thực hiện dưới dạng cùng một mã (chương trình).

Chính xác và làm thế nào để client, Manager EA và assistant EA truyền dữ liệu và đồng bộ cho nhau? Để hiểu điều này, hãy phân tích chức năng WebRequest.

Như bạn đã biết, MetaTrader có hai tùy chọn của chức năng WebRequest. Chúng ta sẽ coi phương thức thứ hai là phổ biến để làm mục tiêu cho phần này (tương tự với MQL4):

An toàn & bảo mật vốn đầu tư tại HotForex
int WebRequest
( 
  const string      method,           // HTTP method 
  const string      url,              // url address 
  const string      headers,          // headers  
  int               timeout,          // timeout 
  const char        &data[],          // HTTP message body array 
  char              &result[],        // array with server response data 
  string            &result_headers   // server response headers 
);

Ta có 5 tham số truyền vào đầu tiên. Chúng được truyền từ mã nguồn gọi đến kernel và xác định nội dung yêu cầu. Hai tham số cuối cùng là những tham số đầu ra. Chúng được truyền từ kernel đến mã gọi và chứa kết quả truy vấn. Vì vậy, việc biến chức năng này thành một chức năng không đồng bộ sẽ đơn giản chỉ là chia nó thành hai thành phần: khởi tạo truy vấn và nhận kết quả:

int WebRequestAsync
( 
  const string      method,           // HTTP method 
  const string      url,              // url address 
  const string      headers,          // headers  
  int               timeout,          // timeout 
  const char        &data[],          // HTTP message body array 
);

int WebRequestAsyncResult
( 
  char              &result[],        // array with server response data 
  string            &result_headers   // server response headers 
);

Trên thực tế, chúng ta cần chuyển thông tin này giữa các chương trình MQL khác nhau. Các chức năng gọi hàm bình thường không phù hợp cho việc này. Để cho các chương trình MQL ‘giao tiếp’ với nhau, MetaTrader có hệ thống trao đổi sự kiện tùy chỉnh mà chúng ta sẽ sử dụng sau đây. Trao đổi sự kiện được thực hiện dựa trên ID người nhận bằng Biểu đồ – nó là duy nhất cho mỗi biểu đồ. Có thể chỉ có một EA trên biểu đồ, nhưng không có giới hạn như vậy trong trường hợp các chỉ số. Điều này có nghĩa là người dùng nên đảm bảo rằng mỗi biểu đồ chứa không nhiều hơn một chỉ báo giao tiếp với Manager EA.

Để việc trao đổi dữ liệu hoạt động, bạn cần đóng gói tất cả các tham số ‘hàm’ vào các tham số sự kiện của người dùng. Cả hai tham số và kết quả yêu cầu có thể chứa một lượng thông tin khá lớn không phù hợp về mặt vật lý trong phạm vi giới hạn của các sự kiện. Ví dụ: ngay cả khi chúng ta quyết định vượt qua phương thức HTTP và URL trong tham số sự kiện chuỗi sparam, việc giới hạn độ dài ở mức 63 ký tự sẽ là một trở ngại trong hầu hết các trường hợp. Điều này có nghĩa là một hệ thống trao đổi sự kiện cần được bổ sung một số loại kho lưu trữ dữ liệu được chia sẻ và chỉ các liên kết đến các bản ghi trong kho lưu trữ này phải được gửi trong các tham số sự kiện. May mắn thay, MetaTrader cung cấp lưu trữ như vậy dưới dạng tài nguyên tùy chỉnh. Trong thực tế, tài nguyên được tạo động từ MQL luôn là hình ảnh. Nhưng một hình ảnh là một kho chứa thông tin nhị phân, nơi bạn có thể viết bất cứ điều gì bạn muốn.

Để đơn giản hóa tác vụ, chúng ta sẽ sử dụng giải pháp được tạo sẵn để ghi và đọc dữ liệu tùy ý vào tài nguyên người dùng – các lớp từ Resource.mqh và ResourceData.mqh được phát triển bởi một thành viên của cộng đồng MQL5.

Chúng ta không cần đi sâu vào cấu trúc bên trong của các lớp phụ trợ này. Điều chính là chúng ta có thể dựa vào lớp RESOURCEDATA được tạo sẵn dưới dạng một hộp đen, sử dụng hàm tạo của nó và một vài hàm phù hợp với chúng ta, như vậy là đủ rồi.

Chúng ta sẽ xem xét điều này chi tiết hơn sau. Bây giờ, hãy giải thích khái niệm tổng thể.

Trình tự tương tác của các phần kiến ​​trúc của chúng ta theo các bước như sau:

  1. Để thực hiện một yêu cầu webrequest không đồng bộ, chương trình MQL của Client nên sử dụng các lớp chúng ta phát triển để đóng gói các tham số yêu cầu vào tài nguyên cục bộ và gửi một sự kiện tùy chỉnh tới Manager EA có liên kết đến tài nguyên; tài nguyên được tạo trong chương trình client và không bị xóa cho đến khi có được kết quả (khi nó không cần thiết)
  2. Manager EA tìm thấy một assistant EA mà nó không bị Manager khác chiếm quyền ở trong nhóm và gửi cho nó một liên kết đến tài nguyên. Và trường hợp này được đánh dấu là chiếm dụng tạm thời và không nhận các yêu cầu khác cho đến khi yêu cầu hiện tại đã được xử lý.
  3. Các tham số của một yêu cầu web từ tài nguyên bên ngoài của client được giải nén trong assistant EA.
  4. Assistant EA gọi hàm WebRequest tiêu chuẩn và chờ câu trả lời (tiêu đề hoặc chuỗi);
  5. Assistant EA đóng gói các kết quả yêu cầu vào tài nguyên cục bộ của Meta Trader và gửi một sự kiện tùy chỉnh tới Manager EA bằng một liên kết đến tài nguyên này.
  6. Manager EA chuyển tiếp sự kiện đến client và giải phóng tài nguyên đã cấp cho assistant EA.
  7. Client nhận được một tin nhắn từ Manager EA và giải nén kết quả của yêu cầu từ assitant EA bên ngoài khác.
  8. Client và assistant có thể xóa tài nguyên local trên Meta Trader.

Kết quả có thể được chuyển hiệu quả hơn ở bước 5 và 6 do thực tế là assistant EA gửi kết quả trực tiếp đến cửa sổ client bỏ qua Manager EA.

Các bước được mô tả ở trên có liên quan đến giai đoạn chính xử lý các yêu cầu HTTP. Bây giờ, đã đến lúc mô tả việc liên kết các phần khác nhau thành một kiến ​​trúc duy nhất. Nó cũng một phần dựa vào các sự kiện người dùng.

Liên kết trung tâm của kiến ​​trúc – Manager EA – được cho là sẽ được khởi chạy thủ công. Bạn chỉ nên làm một lần. Giống như bất kỳ EA đang chạy nào khác, nó tự động phục hồi cùng với biểu đồ sau khi thiết bị đầu cuối khởi động lại. Thiết bị đầu cuối chỉ cho phép một Manager EA tạo request web.

Manager EA tạo ra số lượng cửa sổ phụ trợ cần thiết (sẽ được đặt trong cài đặt) và khởi chạy các phiên bản của chính họ trong đó mà Tìm hiểu về tình trạng assistant của họ nhờ vào giao thức đặc biệt (chi tiết nằm trong phần triển khai).

Bất kỳ assitant nào thông báo cho Manager EA về việc đóng cửa với sự giúp đỡ của một sự kiện đặc biệt. Điều này là cần thiết để duy trì một danh sách liên quan của các assistant có sẵn trong Manager EA. Tương tự, Manager EA thông báo cho các assistant về việc đóng cửa. Đổi lại, các assistant ngừng làm việc và đóng cửa sổ của họ. Các assistant không được sử dụng mà không có Manager EA, trong khi khởi chạy lại trình quản lý chắc chắn sẽ tạo lại các assistant (ví dụ: nếu bạn thay đổi số lượng assistant trong cài đặt).

Windows cho các assistant, giống như các assistant EA, luôn được cho là sẽ được tạo tự động từ Manager EA, và do đó chương trình của chúng ta nên dọn dẹp chúng ngay lập tức. Không khởi chạy assistant EA theo cách thủ công – các đầu vào không tương ứng với trạng thái Manager EA được chương trình coi là lỗi.

Trong quá trình khởi chạy, chương trình MQL của client sẽ khảo sát cửa sổ đầu cuối về sự hiện diện của Manager EA bằng cách sử dụng tin nhắn hàng loạt và chỉ định Biểu đồ của nó trong tham số. Manager EA (nếu tìm thấy) sẽ trả lại ID của cửa sổ của nó cho client. Sau đó, client và Manager EA có thể trao đổi tin nhắn.

Trên đây là những tính năng chính. Hãy bắt đầu với các dòng code.

Bắt đầu

Để đơn giản hóa việc phát triển, hãy tạo một tệp tiêu đề multiweb.mqh nơi chúng ta mô tả tất cả các lớp: một số trong số chúng là phổ biến cho client và ‘máy chủ’, trong khi các tệp khác được kế thừa và cụ thể cho từng vai trò này.

Tạo lớp cơ sở

Hãy bắt đầu từ lớp lưu trữ tài nguyên, ID và biến của từng thành phần. Thể hiện của các lớp bắt nguồn từ nó sẽ được sử dụng trong trình quản lý, trong assistant và trong client. Trong client và trong các assistant, các đối tượng như vậy chủ yếu là cần thiết để lưu trữ các tài nguyên ‘được truyền bởi liên kết’. Bên cạnh đó, lưu ý rằng một số trường hợp đã được tạo trong client để thực hiện nhiều yêu cầu web cùng một lúc. Do đó, việc phân tích trạng thái của các yêu cầu hiện tại (ít nhất là liệu một đối tượng đã bận hay chưa) nên được sử dụng trên toàn bộ client. Trong trình quản lý, các đối tượng này được sử dụng để triển khai nhận dạng và theo dõi trạng thái của assistant. Dưới đây là lớp cơ sở.

class WebWorker
{
  protected:
    long chartID;
    bool busy;
    const RESOURCEDATA<uchar> *resource;
    const string prefix;
    
    const RESOURCEDATA<uchar> *allocate()
    {
      release();
      resource = new RESOURCEDATA<uchar>(prefix + (string)chartID);
      return resource;
    }
    
  public:
    WebWorker(const long id, const string p = "WRP_"): chartID(id), busy(false), resource(NULL), prefix("::" + p)
    {
    }

    ~WebWorker()
    {
      release();
    }
    
    long getChartID() const
    {
      return chartID;
    }
    
    bool isBusy() const
    {
      return busy;
    }
    
    string getFullName() const
    {
      return StringSubstr(MQLInfoString(MQL_PROGRAM_PATH), StringLen(TerminalInfoString(TERMINAL_PATH)) + 5) + prefix + (string)chartID;
    }
    
    virtual void release()
    {
      busy = false;
      if(CheckPointer(resource) == POINTER_DYNAMIC) delete resource;
      resource = NULL;
    }

    static void broadcastEvent(ushort msg, long lparam = 0, double dparam = 0.0, string sparam = NULL)
    {
      long currChart = ChartFirst(); 
      while(currChart != -1)
      {
        if(currChart != ChartID())
        {
          EventChartCustom(currChart, msg, lparam, dparam, sparam); 
        }
        currChart = ChartNext(currChart);
      }
    }
};

Các biến:

  • ChartID – ID của biểu đồ một chương trình MQL đã được đưa ra;
  • busy  – nếu phiên bản hiện tại đang bận xử lý yêu cầu web;
  • resource  – tài nguyên của một đối tượng (lưu trữ dữ liệu ngẫu nhiên); Lớp RESOURCEDATA được lấy từ ResourceData.mqh;
  • prefix  – tiền tố duy nhất cho mỗi trạng thái; Một tiền tố được sử dụng trong tên của tài nguyên. Trong một client cụ thể, nên thực hiện một cài đặt duy nhất như hiển thị bên dưới. Assistant EA sử dụng tiền tố ‘WRR_’ (viết tắt từ Kết quả yêu cầu web) theo mặc định.

Phương thức ‘phân bổ’ sẽ được sử dụng trong các lớp dẫn xuất. Nó tạo ra một đối tượng của tài nguyên loại uchar RESOURCEDATA trong biến ‘resource’. ID biểu đồ cũng được sử dụng để đặt tên tài nguyên, cùng với tiền tố. Tài nguyên có thể được giải phóng bằng phương pháp ‘phát hành’.

Phương thức getFullName nên được đề cập cụ thể, vì nó trả về tên tài nguyên đầy đủ, bao gồm tên chương trình MQL và đường dẫn thư mục hiện tại. Tên đầy đủ được sử dụng để truy cập tài nguyên chương trình của bên thứ ba (chỉ để đọc). Ví dụ: nếu EA multiweb.mq5 nằm trong MQL5 \\ Experts và được khởi chạy trên biểu đồ với ID 129912254742671346, tài nguyên trong đó nhận được tên đầy đủ ‘\\ Experts \\ multiweb.ex5 :: WRR_129912254742671346’. Chúng ta sẽ chuyển các chuỗi như vậy cho các tài nguyên dưới dạng một liên kết bằng cách sử dụng tham số chuỗi sparam của các sự kiện tùy chỉnh.

Phương thức tĩnh BroadcastEvent, gửi tin nhắn đến tất cả các cửa sổ, sẽ được sử dụng trong tương lai để tìm Manager EA.

Để làm việc với một yêu cầu và tài nguyên liên quan trong chương trình client, chúng ta định nghĩa lớp ClientWebWorker có nguồn gốc từ WebWorker (sau đây là mã được viết tắt, các phiên bản đầy đủ nằm trong các tệp đính kèm).

class ClientWebWorker : public WebWorker
{
  protected:
    string _method;
    string _url;
    
  public:
    ClientWebWorker(const long id, const string p = "WRP_"): WebWorker(id, p)
    {
    }

    string getMethod() const
    {
      return _method;
    }

    string getURL() const
    {
      return _url;
    }
    
    bool request(const string method, const string url, const string headers, const int timeout, const uchar &body[], const long managerChartID)
    {
      _method = method;
      _url = url;

      // allocate()? and what's next?
      ...
    }
    
    static void receiveResult(const string resname, uchar &initiator[], uchar &headers[], uchar &text[])
    {
      Print(ChartID(), ": Reading result ", resname);
      
      ...
    }
};

Trước hết, lưu ý rằng phương pháp ‘yêu cầu’ là triển khai thực tế của bước 1 được mô tả ở trên. Ở đây một yêu cầu web được gửi đến Manager EA. Khai báo phương thức tuân theo nguyên mẫu của WebRequestAsync giả định. Phương thức tĩnh receiveResult thực hiện hành động ngược lại từ bước 7. Là đầu vào đầu tiên của ‘tên gọi’, nó nhận được tên đầy đủ của tài nguyên bên ngoài trong đó kết quả yêu cầu được lưu trữ, trong khi ‘bộ khởi tạo’, ‘tiêu đề’ và ‘văn bản’ mảng byte sẽ được điền vào trong phương thức với dữ liệu được giải nén từ tài nguyên.

‘Người khởi xướng’ là gì? Câu trả lời rất đơn giản. Vì tất cả các ‘cuộc gọi’ của chúng ta hiện không đồng bộ (và thứ tự thực hiện của chúng không được đảm bảo), nên chúng ta có thể khớp kết quả với yêu cầu đã gửi trước đó. Do đó, các EA hỗ trợ đóng gói tên đầy đủ của tài nguyên client nguồn được sử dụng để bắt đầu yêu cầu vào tài nguyên phản hồi của họ cùng với dữ liệu thu được từ Internet. Sau khi giải nén, tên sẽ vào tham số ‘bộ khởi tạo’ và có thể được sử dụng để liên kết kết quả với yêu cầu tương ứng.

Phương thức receiveResult là tĩnh, vì nó không sử dụng biến đối tượng – tất cả các kết quả được trả về mã gọi thông qua các tham số.

Cả hai phương pháp đều chứa các hình elip trong đó việc đóng gói và giải nén dữ liệu đến và từ các tài nguyên được yêu cầu. Điều này sẽ được xem xét trong phần tiếp theo.

Yêu cầu đóng gói và yêu cầu kết quả vào tài nguyên

Như chúng ta biết, các tài nguyên được cho là sẽ được xử lý ở cấp thấp hơn bằng cách sử dụng lớp RESOURCEDATA. Đây là một lớp mẫu, có nghĩa là nó chấp nhận một tham số với kiểu dữ liệu mà chúng ta viết và đọc đến hoặc từ một tài nguyên. Vì dữ liệu của chúng ta cũng chứa các chuỗi, nên việc chọn loại uchar nhỏ nhất làm đơn vị lưu trữ là hợp lý. Do đó, đối tượng của lớp uchar RESOURCEDATA được sử dụng làm vùng chứa dữ liệu. Khi tạo tài nguyên, một ‘tên’ duy nhất (cho chương trình) được tạo trong hàm tạo của nó:

RESOURCEDATA<uchar>(const string name)

Chúng ta có thể chuyển tên này (được bổ sung bởi tên chương trình làm tiền tố) trong các sự kiện tùy chỉnh, để các chương trình MQL khác có thể truy cập cùng một tài nguyên. Xin lưu ý rằng tất cả các chương trình khác, ngoại trừ chương trình được tạo tài nguyên, đều có quyền truy cập chỉ đọc.

Dữ liệu được ghi vào tài nguyên bằng toán tử gán quá tải:

void operator=(const uchar &array[]) const

Trong đó ‘mảng’ là một loại mảng chúng ta phải chuẩn bị.

Đọc dữ liệu từ tài nguyên được thực hiện bằng hàm:

int Get(uchar &array[]) const

Ở đây, ‘mảng’ là một tham số đầu ra trong đó nội dung mảng ban đầu được đặt.

Bây giờ, hãy chuyển sang khía cạnh ứng dụng sử dụng tài nguyên để truyền dữ liệu về các yêu cầu HTTP và kết quả của chúng. Chúng ta sẽ tạo một lớp lớp giữa các tài nguyên và mã chính – ResourceMedinator. Lớp này sẽ đóng gói các tham số ‘phương thức’, ‘url’, ‘tiêu đề’, ‘thời gian chờ’ và ‘dữ liệu’ vào mảng byte ‘mảng’ và sau đó ghi vào tài nguyên ở phía client. Về phía máy chủ, nó là để giải nén các tham số từ tài nguyên. Tương tự, lớp này sẽ đóng gói các tham số ‘result’ và ‘result_headers’ của phía máy chủ vào mảng byte ‘mảng’ và ghi vào tài nguyên để đọc nó dưới dạng một mảng và giải nén nó ở phía client.

Trình xây dựng ResourceMedinator chấp nhận con trỏ tới tài nguyên RESOURCEDATA, sau đó sẽ được xử lý bên trong các phương thức. Ngoài ra, ResourceMedoder chứa các cấu trúc hỗ trợ để lưu trữ thông tin meta về dữ liệu. Thật vậy, khi đóng gói và giải nén tài nguyên, chúng ta cần một tiêu đề nhất định chứa kích thước của tất cả các trường ngoài dữ liệu.

Ví dụ: nếu chúng ta chỉ đơn giản sử dụng hàm StringToCharArray để chuyển đổi URL thành một mảng byte, thì khi thực hiện thao tác nghịch đảo bằng CharArrayToString, chúng ta cần đặt độ dài mảng. Mặt khác, không chỉ các byte URL mà cả trường tiêu đề theo sau chúng sẽ được đọc từ mảng. Như bạn có thể nhớ, chúng ta lưu trữ tất cả dữ liệu trong một mảng trước khi truy cập vào tài nguyên. Thông tin meta về độ dài của các trường cũng nên được chuyển đổi thành một chuỗi byte. Chúng ta áp dụng công đoàn cho điều đó.

#define LEADSIZE (sizeof(int)*5) // 5 fields in web-request

class ResourceMediator
{
  private:
    const RESOURCEDATA<uchar> *resource; // underlying asset
    
    // meta-data in header is represented as 5 ints `lengths` and/or byte array `sizes`
    union lead
    {
      struct _l
      {
        int m; // method
        int u; // url
        int h; // headers
        int t; // timeout
        int b; // body
      }
      lengths;
      
      uchar sizes[LEADSIZE];
      
      int total()
      {
        return lengths.m + lengths.u + lengths.h + lengths.t + lengths.b;
      }
    }
    metadata;
  
    // represent int as byte array and vice versa
    union _s
    {
      int x;
      uchar b[sizeof(int)];
    }
    int2chars;
    
    
  public:
    ResourceMediator(const RESOURCEDATA<uchar> *r): resource(r)
    {
    }
    
    void packRequest(const string method, const string url, const string headers, const int timeout, const uchar &body[])
    {
      // fill metadata with parameters data lengths
      metadata.lengths.m = StringLen(method) + 1;
      metadata.lengths.u = StringLen(url) + 1;
      metadata.lengths.h = StringLen(headers) + 1;
      metadata.lengths.t = sizeof(int);
      metadata.lengths.b = ArraySize(body);
      
      // allocate resulting array to fit metadata plus parameters data
      uchar data[];
      ArrayResize(data, LEADSIZE + metadata.total());
      
      // put metadata as byte array at the beginning of the array
      ArrayCopy(data, metadata.sizes);
      
      // put all data fields into the array, one by one
      int cursor = LEADSIZE;
      uchar temp[];
      StringToCharArray(method, temp);
      ArrayCopy(data, temp, cursor);
      ArrayResize(temp, 0);
      cursor += metadata.lengths.m;
      
      StringToCharArray(url, temp);
      ArrayCopy(data, temp, cursor);
      ArrayResize(temp, 0);
      cursor += metadata.lengths.u;
      
      StringToCharArray(headers, temp);
      ArrayCopy(data, temp, cursor);
      ArrayResize(temp, 0);
      cursor += metadata.lengths.h;
      
      int2chars.x = timeout;
      ArrayCopy(data, int2chars.b, cursor);
      cursor += metadata.lengths.t;
      
      ArrayCopy(data, body, cursor);
      
      // store the array in the resource
      resource = data;
    }
    
    ...

Đầu tiên, phương thức packRequest ghi kích thước của tất cả các trường vào cấu trúc ‘siêu dữ liệu’. Sau đó, nội dung của cấu trúc này được sao chép vào đầu mảng ‘dữ liệu’ dưới dạng một mảng byte. Mảng ‘dữ liệu’ sau đó được đặt vào tài nguyên. Kích thước mảng ‘dữ liệu’ được dành riêng dựa trên tổng chiều dài của tất cả các trường và kích thước của cấu trúc với dữ liệu meta. Các tham số loại chuỗi được chuyển đổi thành các mảng bằng StringToCharArray và được sao chép vào mảng kết quả với một sự thay đổi tương ứng, được cập nhật trong biến ‘con trỏ’. Tham số ‘thời gian chờ’ được chuyển đổi thành một mảng ký hiệu bằng cách sử dụng liên kết int2chars. Tham số ‘body’ được sao chép vào mảng ‘nguyên trạng’ vì nó đã là một mảng thuộc loại bắt buộc. Cuối cùng, việc di chuyển nội dung của mảng chung vào tài nguyên được thực hiện trong một chuỗi (như bạn có thể nhớ, toán tử ‘\u003d’ bị quá tải trong lớp RESOURCEDATA):

 resource = data;

Hoạt động ngược lại của việc truy xuất các tham số yêu cầu từ tài nguyên được thực hiện trong phương thức unpackRequest.

void unpackRequest(string &method, string &url, string &headers, int &timeout, uchar &body[])
    {
      uchar array[];
      // fill array with data from resource  
      int n = resource.Get(array);
      Print(ChartID(), ": Got ", n, " bytes in request");
      
      // read metadata from the array
      ArrayCopy(metadata.sizes, array, 0, 0, LEADSIZE);
      int cursor = LEADSIZE;

      // read all data fields, one by one      
      method = CharArrayToString(array, cursor, metadata.lengths.m);
      cursor += metadata.lengths.m;
      url = CharArrayToString(array, cursor, metadata.lengths.u);
      cursor += metadata.lengths.u;
      headers = CharArrayToString(array, cursor, metadata.lengths.h);
      cursor += metadata.lengths.h;
      
      ArrayCopy(int2chars.b, array, 0, cursor, metadata.lengths.t);
      timeout = int2chars.x;
      cursor += metadata.lengths.t;
      
      if(metadata.lengths.b > 0)
      {
        ArrayCopy(body, array, 0, cursor, metadata.lengths.b);
      }
    }
    
    ...

Ở đây công việc chính được thực hiện bởi chuỗi gọi resource.Get (mảng). Sau đó, các byte dữ liệu meta, cũng như tất cả các trường tiếp theo dựa trên chúng, được đọc từng bước từ ‘mảng’.

Kết quả thực hiện yêu cầu được đóng gói và giải nén theo cách tương tự bằng cách sử dụng các phương thức packResponse và unpackResponse (mã đầy đủ được đính kèm bên dưới).

    void packResponse(const string source, const uchar &result[], const string &result_headers);
    void unpackResponse(uchar &initiator[], uchar &headers[], uchar &text[]);

Bây giờ chúng ta có thể quay lại mã nguồn ClientWebWorker và hoàn thành các phương thức ‘request’ và ‘receiveResult’.

class ClientWebWorker : public WebWorker
{
    ...

    bool request(const string method, const string url, const string headers, const int timeout, const uchar &body[], const long managerChartID)
    {
      _method = method;
      _url = url;

      ResourceMediator mediator(allocate());
      mediator.packRequest(method, url, headers, timeout, body);
    
      busy = EventChartCustom(managerChartID, 0 /* TODO: specific message */, chartID, 0.0, getFullName());
      return busy;
    }
    
    static void receiveResult(const string resname, uchar &initiator[], uchar &headers[], uchar &text[])
    {
      Print(ChartID(), ": Reading result ", resname);
      const RESOURCEDATA<uchar> resource(resname);
      ResourceMediator mediator(&resource);
      mediator.unpackResponse(initiator, headers, text);
    }
};

Chúng khá đơn giản do lớp ResourceMedinator đảm nhận tất cả các công việc thường ngày.

Các câu hỏi còn lại là ai và khi nào gọi các phương thức WebWorker, cũng như làm thế nào chúng ta có thể nhận được các giá trị của một số tham số tiện ích, chẳng hạn như managerChartID, trong phương thức ‘request’. Mặc dù tôi hơi chạy về phía trước, tôi khuyên bạn nên phân bổ quản lý tất cả các đối tượng lớp WebWorker cho các lớp cấp cao hơn sẽ hỗ trợ danh sách đối tượng thực tế và trao đổi tin nhắn giữa các chương trình ‘thay mặt’ cho các đối tượng bao gồm các tin nhắn tìm kiếm của Manager EA. Nhưng trước khi chúng ta chuyển sang cấp độ mới này, cần phải hoàn thành một sự chuẩn bị tương tự cho phần ‘máy chủ’.

Lớp cơ bản (tiếp theo)

Chúng ta hãy khai báo đạo hàm tùy chỉnh từ WebWorker để xử lý các yêu cầu không đồng bộ ở phía ‘máy chủ’ (trình quản lý), giống như lớp ClientWebWorker thực hiện điều đó ở phía client.

class ServerWebWorker : public WebWorker
{
  public:
    ServerWebWorker(const long id, const string p = "WRP_"): WebWorker(id, p)
    {
    }
    
    bool transfer(const string resname, const long clientChartID)
    {
      // respond to the client with `clientChartID` that the task in `resname` was accepted
      // and pass the task to this specific worker identified by `chartID` 
      busy = EventChartCustom(clientChartID, TO_MSG(MSG_ACCEPTED), chartID, 0.0, resname)
          && EventChartCustom(chartID, TO_MSG(MSG_WEB), clientChartID, 0.0, resname);
      return busy;
    }
    
    void receive(const string source, const uchar &result[], const string &result_headers)
    {
      ResourceMediator mediator(allocate());
      mediator.packResponse(source, result, result_headers);
    }
};

Các đại biểu phương thức ‘chuyển’ xử lý một yêu cầu đến một phiên bản nhất định của assistant EA theo bước 2 trong chuỗi tương tác tổng thể. Tham số resname là tên tài nguyên thu được từ client, trong khi clientChartID là ID cửa sổ client. Chúng ta có được tất cả các tham số từ các sự kiện tùy chỉnh. Các sự kiện tùy chỉnh, bao gồm MSG_weB, được mô tả bên dưới.

Phương thức ‘nhận’ tạo một tài nguyên cục bộ trong đối tượng hiện tại của WebWorker (cuộc gọi ‘phân bổ’) và viết tên của tài nguyên khởi tạo yêu cầu ban đầu ở đó, cũng như dữ liệu thu được từ Internet (kết quả) và tiêu đề HTTP (result_headers) Đối tượng ‘hòa giải’ của lớp ResourceMedinator. Đây là một phần của bước 5 của trình tự tổng thể.

Vì vậy, chúng ta đã định nghĩa các lớp WebWorker cho cả client và ‘máy chủ’. Trong cả hai trường hợp, những đối tượng này rất có thể sẽ được tạo ra với số lượng lớn. Ví dụ: một client có thể tải xuống nhiều tài liệu cùng một lúc, trong khi về phía Manager EA, ban đầu, mong muốn phân phối đủ số lượng assistant, vì các yêu cầu có thể đến từ nhiều client cùng một lúc. Hãy định nghĩa lớp cơ sở WebWorkersPool để sắp xếp mảng đối tượng. Chúng ta hãy làm cho nó trở thành một khuôn mẫu, bởi vì loại đối tượng được lưu trữ sẽ khác nhau trên client và trên máy chủ của LÊNH (ClientWebWorker và ServerWebWorker, tương ứng).

template<typename T>
class WebWorkersPool
{
  protected:
    T *workers[];
    
  public:
    WebWorkersPool() {}
    
    WebWorkersPool(const uint size)
    {
      // allocate workers; in clients they are used to store request parameters in resources
      ArrayResize(workers, size);
      for(int i = 0; i < ArraySize(workers); i++)
      {
        workers[i] = NULL;
      }
    }
    
    ~WebWorkersPool()
    {
      for(int i = 0; i < ArraySize(workers); i++)
      {
        if(CheckPointer(workers[i]) == POINTER_DYNAMIC) delete workers[i];
      }
    }
    
    int size() const
    {
      return ArraySize(workers);
    }
    
    void operator<<(T *worker)
    {
      const int n = ArraySize(workers);
      ArrayResize(workers, n + 1);
      workers[n] = worker;
    }
    
    T *findWorker(const string resname) const
    {
      for(int i = 0; i < ArraySize(workers); i++)
      {
        if(workers[i] != NULL)
        {
          if(workers[i].getFullName() == resname)
          {
            return workers[i];
          }
        }
      }
      return NULL;
    }
    
    T *getIdleWorker() const
    {
      for(int i = 0; i < ArraySize(workers); i++)
      {
        if(workers[i] != NULL)
        {
          if(ChartPeriod(workers[i].getChartID()) > 0) // check if exist
          {
            if(!workers[i].isBusy())
            {
              return workers[i];
            }
          }
        }
      }
      return NULL;
    }
    
    T *findWorker(const long id) const
    {
      for(int i = 0; i < ArraySize(workers); i++)
      {
        if(workers[i] != NULL)
        {
          if(workers[i].getChartID() == id)
          {
            return workers[i];
          }
        }
      }
      return NULL;
    }
    
    bool revoke(const long id)
    {
      for(int i = 0; i < ArraySize(workers); i++)
      {
        if(workers[i] != NULL)
        {
          if(workers[i].getChartID() == id)
          {
            if(CheckPointer(workers[i]) == POINTER_DYNAMIC) delete workers[i];
            workers[i] = NULL;
            return true;
          }
        }
      }
      return false;
    }
    
    int available() const
    {
      int count = 0;
      for(int i = 0; i < ArraySize(workers); i++)
      {
        if(workers[i] != NULL)
        {
          count++;
        }
      }
      return count;
    }
    
    T *operator[](int i) const
    {
      return workers[i];
    }
    
};

Ý tưởng đằng sau các phương pháp rất đơn giản. Hàm tạo và hàm hủy phân bổ và giải phóng mảng các trình xử lý kích thước đã chỉ định. Nhóm các phương thức findWorker và getIdleWorker tìm kiếm các đối tượng trong mảng theo các tiêu chí khác nhau. Toán tử ‘toán tử’ cho phép thêm các đối tượng một cách linh hoạt, trong khi phương thức ‘thu hồi’ cho phép loại bỏ chúng một cách linh hoạt.

Nhóm xử lý về phía client cần có một số tính cụ thể (đặc biệt, liên quan đến xử lý sự kiện). Do đó, chúng ta mở rộng lớp cơ sở bằng cách sử dụng lớp ClientWebWorkersPool.

template<typename T>
class ClientWebWorkersPool: public WebWorkersPool<T>
{
  protected:
    long   managerChartID;
    short  managerPoolSize;
    string name;
    
  public:
    ClientWebWorkersPool(const uint size, const string prefix): WebWorkersPool(size)
    {
      name = prefix;
      // try to find WebRequest manager chart
      WebWorker::broadcastEvent(TO_MSG(MSG_DISCOVER), ChartID());
    }
    
    bool WebRequestAsync(const string method, const string url, const string headers, int timeout, const char &data[])
    {
      T *worker = getIdleWorker();
      if(worker != NULL)
      {
        return worker.request(method, url, headers, timeout, data, managerChartID);
      }
      return false;
    }
    
    void onChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
    {
      if(MSG(id) == MSG_DONE) // async request is completed with result or error
      {
        Print(ChartID(), ": Result code ", (long)dparam);
    
        if(sparam != NULL)
        {
          // read data from the resource with name in sparam
          uchar initiator[], headers[], text[];
          ClientWebWorker::receiveResult(sparam, initiator, headers, text);
          string resname = CharArrayToString(initiator);
          
          T *worker = findWorker(resname);
          if(worker != NULL)
          {
            worker.onResult((long)dparam, headers, text);
            worker.release();
          }
        }
      }
      
      ...
      
      else
      if(MSG(id) == MSG_HELLO) // manager is found as a result of MSG_DISCOVER broadcast
      {
        if(managerChartID == 0 && lparam != 0)
        {
          if(ChartPeriod(lparam) > 0)
          {
            managerChartID = lparam;
            managerPoolSize = (short)dparam;
            for(int i = 0; i < ArraySize(workers); i++)
            {
              workers[i] = new T(ChartID(), name + (string)(i + 1) + "_");
            }
          }
        }
      }
    }
    
    bool isManagerBound() const
    {
      return managerChartID != 0;
    }
};

Các biến:

  • ManagerChartID – ID của một cửa sổ nơi tìm thấy trình quản lý làm việc;
  • ManagerPoolSize – kích thước ban đầu của mảng đối tượng xử lý;
  • name – tiền tố chung cho các tài nguyên trong tất cả các đối tượng nhóm.

Trao đổi thông điệp

Trong hàm tạo của ClientWebWorkersPool, chúng ta thấy lệnh gọi của WebWorker :: BroadcastEvent (TO_MSG (MSG_DISCOVER), ChartID ()) gửi sự kiện MSG_DISCOVER tới tất cả các cửa sổ thông qua ID của cửa sổ hiện tại trong tham số sự kiện. MSG_DISCOVER là một giá trị dành riêng: nó phải được xác định ở đầu cùng một tệp tiêu đề cùng với các loại thông báo khác mà các chương trình sẽ trao đổi.

#define MSG_DEINIT   1 // tear down (manager <-> worker)
#define MSG_WEB      2 // start request (client -> manager -> worker)
#define MSG_DONE     3 // request is completed (worker -> client, worker -> manager)
#define MSG_ERROR    4 // request has failed (manager -> client, worker -> client)
#define MSG_DISCOVER 5 // find the manager (client -> manager)
#define MSG_ACCEPTED 6 // request is in progress (manager -> client)
#define MSG_HELLO    7 // the manager is found (manager -> client)

Các ý kiến ​​đánh dấu hướng một tin nhắn được gửi vào.

Macro TO_MSG được thiết kế để chuyển đổi ID được liệt kê thành mã sự kiện thực so với giá trị cơ sở do người dùng chọn ngẫu nhiên. Chúng ta sẽ nhận được thông qua đầu vào MessageBroadcast.

sinput uint MessageBroadcast = 1;
 
#define TO_MSG(X) ((ushort)(MessageBroadcast + X))

Cách tiếp cận này cho phép di chuyển tất cả các sự kiện vào bất kỳ phạm vi miễn phí nào bằng cách thay đổi giá trị cơ sở. Lưu ý rằng các sự kiện tùy chỉnh cũng có thể được sử dụng trong thiết bị đầu cuối. Do đó, điều quan trọng là tránh va chạm.

Đầu vào MessageBroadcast sẽ xuất hiện trong tất cả các chương trình MQL của chúng ta có tệp multiweb.mqh, tức là trong các client và trong trình quản lý. Chỉ định cùng giá trị MessageBroadcast khi khởi chạy trình quản lý và client.

Hãy quay trở lại lớp ClientWebWorkersPool. Phương thức onChartEvent chiếm một vị trí đặc biệt. Nó được gọi từ trình xử lý sự kiện OnChartEvent tiêu chuẩn . Một loại sự kiện được truyền trong tham số ‘id’. Vì chúng ta nhận được mã từ hệ thống dựa trên giá trị cơ sở đã chọn, chúng ta nên sử dụng macro MSG “nhân đôi” để chuyển đổi lại thành phạm vi MSG _ ***:

#define MSG(x) (x - MessageBroadcast - CHARTEVENT_CUSTOM)

Ở đây CHARTEVENT_CUSTOM là sự khởi đầu của phạm vi cho tất cả các sự kiện tùy chỉnh trong thiết bị đầu cuối.

Như chúng ta có thể thấy, phương thức onChartEvent trong ClientWebWorkersPool xử lý một số thông báo được đề cập ở trên. Ví dụ: Manager EA nên trả lời bằng tin nhắn MSG_HELLO gửi tin nhắn hàng loạt MSG_DISCOVER. Trong trường hợp này, ID cửa sổ trình quản lý được truyền trong tham số lparam, trong khi số lượng assistant có sẵn được truyền trong tham số dparam. Khi Manager EA được phát hiện, nhóm sẽ lấp đầy mảng ‘công nhân’ trống với các đối tượng thực của loại được yêu cầu. ID cửa sổ hiện tại, cũng như tên tài nguyên duy nhất trong mỗi đối tượng được truyền cho hàm tạo đối tượng. Cái sau bao gồm tiền tố ‘tên’ chung và số sê-ri trong mảng.

Sau khi trường managerChartID nhận được một giá trị có ý nghĩa, có thể gửi yêu cầu đến Manager EA. Phương thức ‘request’ được dành riêng cho phương thức đó trong lớp ClientWebWorker, trong khi việc sử dụng nó được thể hiện trong phương thức WebRequestAsync từ nhóm. Đầu tiên, WebRequestAsync tìm thấy một đối tượng xử lý miễn phí bằng cách sử dụng getIdleWorker và sau đó gọi worker.request (phương thức, url, tiêu đề, thời gian chờ, dữ liệu, managerChartID) cho nó. Bên trong phương thức ‘request’, chúng ta có một nhận xét liên quan đến việc lựa chọn mã thông báo đặc biệt để gửi một sự kiện. Bây giờ, sau khi xem xét hệ thống con sự kiện, chúng ta có thể tạo phiên bản cuối cùng của phương thức ClientWebWorker :: request:

class ClientWebWorker : public WebWorker
{
    ...

    bool request(const string method, const string url, const string headers, const int timeout, const uchar &body[], const long managerChartID)
    {
      _method = method;
      _url = url;

      ResourceMediator mediator(allocate());
      mediator.packRequest(method, url, headers, timeout, body);
    
      busy = EventChartCustom(managerChartID, TO_MSG(MSG_WEB), chartID, 0.0, getFullName());
      return busy;
    }
    
    ...
};

MSG_weB là một thông báo về việc thực hiện một yêu cầu web. Sau khi nhận được nó, Manager EA nên tìm một assistant EA miễn phí và chuyển tên tài nguyên client (sparam) cho nó với các tham số yêu cầu, cũng như ID cửa sổ client chartID (lparam).

Assistant thực hiện yêu cầu và trả về kết quả cho client bằng cách sử dụng sự kiện MSG_DONE (nếu thành công) hoặc mã lỗi sử dụng MSG_ERROR (trong trường hợp có vấn đề). Mã kết quả (hoặc lỗi) được truyền cho dparam, trong khi bản thân kết quả được đóng gói vào một tài nguyên nằm trong assistant EA dưới tên được truyền cho sparam. Trong nhánh MSG_DONE, chúng ta thấy cách dữ liệu được truy xuất từ ​​tài nguyên bằng cách gọi hàm ClientWebWorker :: receiveResult (sparam, initator, headers, text) được xem xét trước đó. Sau đó, việc tìm kiếm đối tượng xử lý client (findWorker) được thực hiện bởi tên tài nguyên của bộ khởi tạo yêu cầu và một vài phương thức được gọi trên một đối tượng được phát hiện:

    T *worker = findWorker(resname);
    if(worker != NULL)
    {
      worker.onResult((long)dparam, headers, text);
      worker.release();
    }

Chúng ta đã biết phương pháp ‘phát hành’ – nó giải phóng tài nguyên không cần thiết. OnResult là gì? Nếu chúng ta xem mã nguồn đầy đủ, chúng ta sẽ thấy rằng lớp ClientWebWorker có hai hàm ảo mà không cần thực hiện: onResult và onError. Điều này làm cho lớp trừu tượng. Mã client sẽ mô tả lớp dẫn xuất của nó từ ClientWebWorker và cung cấp triển khai. Tên của các phương thức ngụ ý rằng onResult được gọi nếu kết quả được nhận thành công, trong khi onError được gọi trong trường hợp có lỗi. Điều này cung cấp phản hồi giữa các lớp làm việc của các yêu cầu không đồng bộ và mã chương trình client sử dụng chúng. Nói cách khác, chương trình client không cần biết gì về các thông điệp mà kernel sử dụng bên trong:

Hãy xem mã nguồn của client (multiwebclient.mq5).

assistant EA

EA thử nghiệm là gửi một số yêu cầu thông qua API multiweb dựa trên dữ liệu được nhập bởi người dùng. Để đạt được điều này, chúng ta cần bao gồm tệp tiêu đề và thêm các đầu vào:

sinput string Method = "GET";
sinput string URL = "https://google.com/,https://ya.ru,https://www.startpage.com/";
sinput string Headers = "User-Agent: n/a";
sinput int Timeout = 5000;

#include <multiweb.mqh>

Cuối cùng, tất cả các tham số được dự định để cấu hình các yêu cầu HTTP được thực hiện. Trong danh sách URL, chúng ta có thể liệt kê một số địa chỉ được phân tách bằng dấu phẩy để đánh giá tính song song và tốc độ thực hiện yêu cầu. Tham số URL được chia thành các địa chỉ bằng hàm StringSplit trong OnInit, như sau:

int urlsnum;
string urls[];
  
void OnInit()
{
  // get URLs for test requests
  urlsnum = StringSplit(URL, ',', urls);
  ...
}

Bên cạnh đó, chúng ta cần tạo một nhóm các đối tượng xử lý yêu cầu (ClientWebWorkersPool) trong OnInit. Nhưng để làm điều này, chúng ta cần mô tả lớp của chúng ta bắt nguồn từ ClientWebWorker.

class MyClientWebWorker : public ClientWebWorker
{
  public:
    MyClientWebWorker(const long id, const string p = "WRP_"): ClientWebWorker(id, p)
    {
    }
    
    virtual void onResult(const long code, const uchar &headers[], const uchar &text[]) override
    {
      Print(getMethod(), " ", getURL(), "\nReceived ", ArraySize(headers), " bytes in header, ", ArraySize(text), " bytes in document");
      // uncommenting this leads to potentially bulky logs
      // Print(CharArrayToString(headers));
      // Print(CharArrayToString(text));
    }

    virtual void onError(const long code) override
    {
      Print("WebRequest error code ", code);
    }
};

Mục tiêu duy nhất của nó là đăng nhập trạng thái và thu được dữ liệu. Bây giờ chúng ta có thể tạo một nhóm các đối tượng như vậy trong OnInit.

ClientWebWorkersPool<MyClientWebWorker> *pool = NULL;

void OnInit()
{
  ...
  pool = new ClientWebWorkersPool<MyClientWebWorker>(urlsnum, _Symbol + "_" + EnumToString(_Period) + "_");
  Comment("Click the chart to start downloads");
}

Như bạn có thể thấy, nhóm được tham số hóa bởi lớp MyClientWebWorker, điều này cho phép tạo các đối tượng của chúng ta từ mã thư viện. Kích thước mảng được chọn bằng với số lượng địa chỉ đã nhập. Điều này là hợp lý cho các mục đích trình diễn: một số nhỏ hơn có nghĩa là một hàng đợi xử lý và làm mất uy tín của ý tưởng thực hiện song song, trong khi một số lớn hơn sẽ gây lãng phí tài nguyên. Trong các dự án thực tế, kích thước nhóm không phải bằng số lượng tác vụ, nhưng điều này đòi hỏi phải có ràng buộc thuật toán bổ sung.

Tiền tố cho tài nguyên được đặt dưới dạng kết hợp tên của biểu tượng làm việc và thời gian biểu đồ.

Cảm ứng cuối cùng về khởi tạo là tìm kiếm cửa sổ trình quản lý. Như bạn nhớ, việc tìm kiếm được thực hiện bởi chính nhóm (lớp ClientWebWorkersPool). Mã client chỉ cần đảm bảo rằng Manager EA được tìm thấy. Đối với các mục đích này, chúng ta hãy thiết lập một số thời gian hợp lý, trong đó thông điệp về trình quản lý tìm kiếm và “phản hồi” cần được đảm bảo để đạt được các mục tiêu. Hãy để nó là 5 giây. Tạo một bộ đếm thời gian cho thời gian này:

void OnInit()
{
  ...
  // wait for manager negotiation for 5 seconds maximum
  EventSetTimer(5);
}

Kiểm tra xem trình quản lý có mặt trong trình xử lý hẹn giờ không. Hiển thị cảnh báo nếu kết nối không được thiết lập.

void OnTimer()
{
  // if the manager did not respond during 5 seconds, it seems missing
  EventKillTimer();
  if(!pool.isManagerBound())
  {
    Alert("WebRequest Pool Manager (multiweb) is not running");
  }
}

Đừng quên xóa đối tượng pool trong trình xử lý OnDeinit.

void OnDeinit(const int reason)
{
  delete pool;
  Comment("");
}

Để cho nhóm xử lý tất cả các thông báo dịch vụ mà không có sự tham gia của chúng ta, bao gồm, trước hết, tìm kiếm Manager EA, hãy sử dụng trình xử lý sự kiện biểu đồ OnChartEvent tiêu chuẩn:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 
{
  if(id == CHARTEVENT_CLICK) // initiate test requests by simple user action
  {
    ...
  }
  else
  {
    // this handler manages all important messaging behind the scene
    pool.onChartEvent(id, lparam, dparam, sparam);
  }
}

Tất cả các sự kiện, ngoại trừ CHARTEVENT_CLICK, được gửi đến nhóm nơi thực hiện các hành động thích hợp dựa trên phân tích mã của các sự kiện được áp dụng (đoạn onChartEvent đã được cung cấp ở trên).

Sự kiện CHARTEVENT_CLICK có tính tương tác và được sử dụng trực tiếp để khởi chạy quá trình tải xuống. Trong trường hợp đơn giản nhất, nó có thể trông như sau:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 
{
  if(id == CHARTEVENT_CLICK) // initiate test requests by simple user action
  {
    if(pool.isManagerBound())
    {
      uchar Body[];

      for(int i = 0; i < urlsnum; i++)
      {
        pool.WebRequestAsync(Method, urls[i], Headers, Timeout, Body);
      }
    }
    ...

Mã đầy đủ của ví dụ dài hơn một chút vì nó cũng có tính năng logic để tính thời gian thực hiện và so sánh nó với một cuộc gọi tuần tự của WebRequest tiêu chuẩn cho cùng một bộ địa chỉ.

Quản lý EA và assistant EA

Cuối cùng chúng ta đã đạt đến phần “máy chủ”. Vì các cơ chế cơ bản đã được triển khai trong tệp tiêu đề, mã của Manager EA và assistant không quá phức tạp như người ta tưởng.

Như bạn có thể nhớ, chúng ta chỉ có một EA làm việc như một Manager EA hoặc là một assistant (tệp multiweb.mq5). Như trong trường hợp của client, chúng ta bao gồm tệp tiêu đề và khai báo các tham số đầu vào:

sinput uint WebRequestPoolSize = 3;
sinput ulong ManagerChartID = 0;

#include <multiweb.mqh>

WebRequestPoolSize là một số cửa sổ phụ mà Manager EA nên tạo để khởi chạy assistant trên chúng.

ManagerChartID là ID cửa sổ trình quản lý. Tham số này chỉ có thể sử dụng như một assistant và được điền đầy đủ với Manager EA khi assistant được khởi chạy tự động từ mã nguồn. Điền vào Trình quản lý thủ công khi khởi chạy trình quản lý được coi là một lỗi.

Thuật toán được xây dựng xung quanh hai biến toàn cục:

bool manager;
WebWorkersPool<ServerWebWorker> pool;

Cờ logic của ‘Manager EA’ cho biết vai trò của thể hiện EA hiện tại. Biến ‘pool’ là một mảng các đối tượng xử lý các tác vụ đến. WebWorkersPool được tiêu biểu hóa bởi lớp ServerWebWorker được mô tả ở trên. Mảng không được khởi tạo trước vì việc điền vào nó phụ thuộc vào vai trò.

Phiên bản khởi chạy đầu tiên (được xác định trong OnInit) nhận vai trò Manager EA.

const string GVTEMP = "WRP_GV_TEMP";

int OnInit()
{
  manager = false;
  
  if(!GlobalVariableCheck(GVTEMP))
  {
    // when first instance of multiweb is started, it's treated as manager
    // the global variable is a flag that the manager is present
    if(!GlobalVariableTemp(GVTEMP))
    {
      FAILED(GlobalVariableTemp);
      return INIT_FAILED;
    }
    
    manager = true;
    GlobalVariableSet(GVTEMP, 1);
    Print("WebRequest Pool Manager started in ", ChartID());
  }
  else
  {
    // all next instances of multiweb are workers/helpers
    Print("WebRequest Worker started in ", ChartID(), "; manager in ", ManagerChartID);
  }
  
  // use the timer for delayed instantiation of workers
  EventSetTimer(1);
  return INIT_SUCCEEDED;
}

EA kiểm tra sự hiện diện của một biến toàn cầu đặc biệt của thiết bị đầu cuối. Nếu nó vắng mặt, EA tự gán cho mình Manager EA và tạo ra một biến toàn cục như vậy. Nếu biến đã có sẵn, thì Manager EA cũng vậy, và do đó, trường hợp này trở thành assistant. Xin lưu ý rằng biến toàn cục là tạm thời, có nghĩa là nó không được lưu khi thiết bị đầu cuối được khởi động lại. Nhưng nếu Manager EA bị bỏ lại trên bất kỳ biểu đồ nào, nó sẽ tạo lại biến.

Bộ định thời sau đó được đặt thành một giây, vì việc khởi tạo biểu đồ phụ thường mất vài giây và thực hiện nó từ OnInit không phải là giải pháp tốt nhất. Điền vào nhóm trong trình xử lý sự kiện hẹn giờ:

void OnTimer()
{
  EventKillTimer();
  if(manager)
  {
    if(!instantiateWorkers())
    {
      Alert("Workers not initialized");
    }
    else
    {
      Comment("WebRequest Pool Manager ", ChartID(), "\nWorkers available: ", pool.available());
    }
  }
  else // worker
  {
    // this is used as a host of resource storing response headers and data
    pool << new ServerWebWorker(ChartID(), "WRR_");
  }
}

Trong trường hợp có vai trò assistant, một đối tượng xử lý ServerWebWorker khác chỉ đơn giản được thêm vào mảng. Trường hợp Manager EA phức tạp hơn và được sắp xếp theo chức năng tức thờiWorkers riêng biệt. Chúng ta hãy xem nó.

bool instantiateWorkers()
{
  MqlParam Params[4];
  
  const string path = MQLInfoString(MQL_PROGRAM_PATH);
  const string experts = "\\MQL5\\";
  const int pos = StringFind(path, experts);
  
  // start itself again (in another role as helper EA)
  Params[0].string_value = StringSubstr(path, pos + StringLen(experts));
  
  Params[1].type = TYPE_UINT;
  Params[1].integer_value = 1; // 1 worker inside new helper EA instance for returning results to the manager or client

  Params[2].type = TYPE_LONG;
  Params[2].integer_value = ChartID(); // this chart is the manager

  Params[3].type = TYPE_UINT;
  Params[3].integer_value = MessageBroadcast; // use the same custom event base number
  
  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;
}

Hàm này sử dụng thư viện bên thứ ba Expert được phát triển bởi người bạn cũ của chúng ta – thành viên của cộng đồng MQL5 fxsaber , do đó, một tệp tiêu đề tương ứng đã được thêm vào đầu mã nguồn.

#include <fxsaber \ Expert.mqh>

Thư viện Expert cho phép bạn tự động tạo các mẫu tpl với các tham số EA được chỉ định và áp dụng chúng cho các biểu đồ được chỉ định, dẫn đến việc khởi chạy EA. Trong trường hợp của chúng ta, các tham số của tất cả các assistant EA là như nhau, vì vậy danh sách của chúng được tạo một lần trước khi tạo một số lượng cửa sổ được chỉ định.

Tham số 0 chỉ định đường dẫn đến tệp EA thực thi, tức là chính nó. Tham số 1 là WebRequestPoolSize. Nó bằng 1 ở mỗi assistant. Như tôi đã đề cập, đối tượng xử lý chỉ cần trong assistant để lưu trữ tài nguyên với kết quả yêu cầu HTTP. Mỗi assistant xử lý yêu cầu bằng một WebRequest chặn, tức là chỉ có một đối tượng xử lý được sử dụng tối đa. Tham số 2 – ID cửa sổ ManagerChartID. Tham số 3 – giá trị cơ bản cho mã tin nhắn (Tham số MessageBroadcast được lấy từ multiweb.mqh).

Hơn nữa, các biểu đồ trống được tạo trong vòng lặp với sự trợ giúp của ChartOpen và các assistant EA được khởi chạy trong đó bằng EXPERT :: Run (biểu đồ, Params). Đối tượng xử lý ServerWebWorker (biểu đồ) được tạo cho mỗi cửa sổ mới và được thêm vào nhóm. Trong trình quản lý, các đối tượng xử lý không có gì khác hơn là liên kết đến ID cửa sổ của assistant và trạng thái của chúng, vì các yêu cầu HTTP không được thực thi trong chính trình quản lý và không có tài nguyên nào được tạo cho chúng.

Các tác vụ đến được xử lý dựa trên các sự kiện của người dùng trong OnChartEvent.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 
{
  if(MSG(id) == MSG_DISCOVER) // a worker EA on new client chart is initialized and wants to bind to this manager
  {
    if(manager && (lparam != 0))
    {
      // only manager responds with its chart ID, lparam is the client chart ID
      EventChartCustom(lparam, TO_MSG(MSG_HELLO), ChartID(), pool.available(), NULL);
    }
  }
  else
  if(MSG(id) == MSG_WEB) // a client has requested a web download
  {
    if(lparam != 0)
    {
      if(manager)
      {
        // the manager delegates the work to an idle worker
        // lparam is the client chart ID, sparam is the client resource
        if(!transfer(lparam, sparam))
        {
          EventChartCustom(lparam, TO_MSG(MSG_ERROR), ERROR_NO_IDLE_WORKER, 0.0, sparam);
        }
      }
      else
      {
        // the worker does actually process the web request
        startWebRequest(lparam, sparam);
      }
    }
  }
  else
  if(MSG(id) == MSG_DONE) // a worker identified by chart ID in lparam has finished its job
  {
    WebWorker *worker = pool.findWorker(lparam);
    if(worker != NULL)
    {
      // here we're in the manager, and the pool hold stub workers without resources
      // so this release is intended solely to clean up busy state
      worker.release();
    }
  }
}

Trước hết, như một phản hồi cho MSG_DISCOVER thu được từ client có ID lparam, Manager EA trả về thông báo MSG_HELLO chứa ID cửa sổ của nó.

Khi nhận được MSG_weB, lparam nên chứa ID cửa sổ của client đã gửi yêu cầu, trong khi sparam nên chứa tên của tài nguyên với các tham số yêu cầu được đóng gói. Làm quản lý, mã cố gắng chuyển nhiệm vụ với các tham số này cho assistant nhàn rỗi bằng cách gọi hàm ‘chuyển’ (được mô tả bên dưới) và do đó thay đổi trạng thái của đối tượng được chọn thành “bận”. Nếu không có assistant nhàn rỗi, sự kiện MSG_ERROR được gửi đến client với mã ERROR_NO_IDLE_WORKER. Assistant thực hiện yêu cầu HTTP trong hàm startWebRequest.

Sự kiện MSG_DONE đến Manager EA từ assistant khi người sau tải lên tài liệu được yêu cầu. Manager EA tìm thấy đối tượng thích hợp bằng ID assistant trong lparam và vô hiệu hóa trạng thái “bận” của nó bằng cách gọi phương thức ‘phát hành’. Như đã đề cập, assistant sẽ gửi kết quả hoạt động của nó trực tiếp đến client.

Mã nguồn đầy đủ cũng chứa sự kiện MSG_DEINIT liên quan chặt chẽ đến việc xử lý OnDeinit. Ý tưởng là các assistant được thông báo về việc loại bỏ Manager EA và, đáp lại, tự dỡ và đóng cửa sổ của họ, trong khi Manager EA được thông báo về việc loại bỏ assistant và xóa nó khỏi nhóm quản lý. Tôi tin rằng, bạn có thể tự hiểu về cơ chế này.

Hàm ‘transfer’ tìm kiếm một đối tượng miễn phí và gọi phương thức ‘transfer’ của nó (đã thảo luận ở trên).

bool transfer(const long returnChartID, const string resname)
{
  ServerWebWorker *worker = pool.getIdleWorker();
  if(worker == NULL)
  {
    return false;
  }
  return worker.transfer(resname, returnChartID);
}

Hàm startWebRequest được định nghĩa như sau:

void startWebRequest(const long returnChartID, const string resname)
{
  const RESOURCEDATA<uchar> 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)
  {
    // create resource with results to pass back to the client via custom event
    ((ServerWebWorker *)pool[0]).receive(resname, result, result_headers);
    // first, send MSG_DONE to the client with resulting resource
    EventChartCustom(returnChartID, TO_MSG(MSG_DONE), ChartID(), (double)code, pool[0].getFullName());
    // second, send MSG_DONE to the manager to set corresponding worker to idle state
    EventChartCustom(ManagerChartID, TO_MSG(MSG_DONE), ChartID(), (double)code, NULL);
  }
  else
  {
    // error code in dparam
    EventChartCustom(returnChartID, TO_MSG(MSG_ERROR), ERROR_MQL_WEB_REQUEST, (double)GetLastError(), resname);
    EventChartCustom(ManagerChartID, TO_MSG(MSG_DONE), ChartID(), (double)GetLastError(), NULL);
  }
}

Bằng cách sử dụng ResourceMedoder, hàm sẽ giải nén các tham số yêu cầu và gọi hàm MQL WebRequest tiêu chuẩn. Nếu chức năng được thực thi mà không có lỗi MQL, kết quả sẽ được gửi cho client. Để làm điều đó, chúng được đóng gói vào một tài nguyên cục bộ bằng phương thức ‘receive’ (được mô tả ở trên) và tên của nó được truyền với thông báo MSG_DONE trong tham số sparam của hàm EventChartCustom. Lưu ý rằng các lỗi HTTP (ví dụ: trang 404 không hợp lệ hoặc lỗi máy chủ web 501) cũng rơi vào đây – client nhận được mã HTTP trong tham số dparam và phản hồi các tiêu đề HTTP trong tài nguyên cho phép bạn phân tích tình huống.

Nếu cuộc gọi WebRequest kết thúc với lỗi MQL, client sẽ nhận được thông báo MSG_ERROR với mã ERROR_MQL_weB_REQUEST, trong khi kết quả GetLastError được đặt cho dparam. Do tài nguyên cục bộ không được điền trong trường hợp này, nên tên của tài nguyên nguồn được truyền trực tiếp trong tham số sparam, do đó, một thể hiện nhất định của đối tượng xử lý với tài nguyên vẫn có thể được xác định ở phía client.

Kiểm tra

Kiểm tra phức tạp phần mềm được thực hiện có thể được thực hiện như sau.

Đầu tiên, mở cài đặt thiết bị đầu cuối và chỉ định tất cả các máy chủ sẽ được truy cập trong danh sách URL được phép trên tab Chuyên gia.

Tiếp theo, khởi chạy EA multiweb và thiết lập 3 assistant trong các đầu vào. Kết quả là, 3 cửa sổ mới được mở có cùng một EA đa mạng được đưa ra trong một vai trò khác. Vai trò EA được hiển thị trong bình luận ở góc trên bên trái của cửa sổ.

Bây giờ, hãy khởi chạy EA client nhiều mạng trên biểu đồ khác và nhấp vào biểu đồ một lần. Với các cài đặt mặc định, nó khởi tạo 3 yêu cầu web song song và ghi chẩn đoán vào nhật ký, bao gồm kích thước của dữ liệu thu được và thời gian chạy. Nếu tham số đặc biệt TestSyncRequests còn lại là ‘true’, các yêu cầu tuần tự của cùng một trang sẽ được thực thi bằng WebRequest tiêu chuẩn bên cạnh các yêu cầu web song song thông qua trình quản lý. Điều này được thực hiện để so sánh tốc độ thực hiện của hai tùy chọn. Theo quy định, việc xử lý song song nhanh hơn nhiều lần so với xử lý tuần tự – từ sqrt (N) đến N, trong đó N là một số assistant có sẵn.

Nhật ký mẫu được hiển thị dưới đây.

01 : 16 : 50,587     multiweb (EURUSD, H1) OnInit 129912254742671339 
01 : 16 : 50,587     multiweb (EURUSD, H1) WebRequest Pool quản lý bắt đầu trong  129912254742671339 
01 : 16 : 52,345     multiweb (EURUSD, H1) OnInit 129912254742671345 
01 : 16 : 52,345     multiweb ( EURUSD, H1) Công nhân WebRequest bắt đầu vào năm  129912254742671345 ; quản lý trong  129912254742671339 
01 : 16 : 52.757    multiweb (EURUSD, H1) OnInit 129912254742671346 
01 : 16 : 52.757     multiweb (EURUSD, H1) Công nhân WebRequest bắt đầu vào năm  129912254742671346 ; Manager EA trong  129912254742671339 
01 : 16 : 53.247     multiweb (EURUSD, H1) OnInit 129912254742671347 
01 : 16 : 53.247     multiweb (EURUSD, H1) Công nhân WebRequest bắt đầu vào  129912254742671347 ; quản lý trong  129912254742671339 
01 : 17 : 16.029    multiweb (EURUSD, H1) Pool quản lý chuyển \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_1_129560567193673862
 01 : 17 : 16,029     multiweb (EURUSD, H1)     129912254742671345 : Yêu cầu Reading \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_1_129560567193673862
 01 : 17 : 16,029     multiweb (EURUSD , H1)     129912254742671345 : Nhận 64 byte theo yêu cầu
 01 : 17 : 16.029     multiweb (EURUSD, H1)     129912254742671345 : NHẬN https: //google.com/ Tác nhân người dùng: n / a 5000 
01: 17 : 16,030     multiweb (EURUSD, H1) Pool quản lý chuyển \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_2_129560567193673862
 01 : 17 : 16,030     multiweb (EURUSD, H1)     129912254742671346 : Đọc yêu cầu \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_2_129560567193673862
 01 : 17 : 16,030     multiwebclient (GBPJPY, M5) Accepted: \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_1_129560567193673862 sau 0 lần thử lại
 01 : 17 : 16,031     multiweb (EURUSD, H1)     129912254742671346 : Chấn60 byte trong yêu cầu
 01 : 17 : 16.031     multiweb (EURUSD, H1)     129912254742671346 : GET https: //ya.ru Tác nhân người dùng: n / a 5000 
01 : 17 : 16.031     multiweb (EURUSD, H1) Trình quản lý nhóm chuyển giao \ Experts \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_3_129560567193673862
 01 : 17 : 16,031     multiwebclient (GBPJPY, M5) Accepted: \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_2_129560567193673862 sau 0 lần thử lại
 01 : 17 : 16,031    multiwebclient (GBPJPY, M5) Accepted: \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_3_129560567193673862 sau 0 lần thử lại
 01 : 17 : 16,031     multiweb (EURUSD, H1)     129912254742671347 : Đọc yêu cầu \ Các chuyên gia \ multiwebclient.ex5 :: GBPJPY_PERIOD_M5_3_129560567193673862
 01 : 17 : 16,032     multiweb (EURUSD, H1)     129912254742671347 : Nhận 72 byte theo yêu cầu
 01 : 17 : 16.032     multiweb (EURUSD, H1)     129912254742671347 : NHẬN https://www.startpage.com/ User-Agent: n / a 5000 
01 : 17 : 16,296     multiwebclient (GBPJPY, M5)       129560567193673862 : Kết quả đang 200 
01 : 17 : 16,296     multiweb (EURUSD, H1) Kết quả đang từ  129912254742671346 : 200 , bây giờ nhàn rỗi
 01 : 17 : 16.297     multiweb (EURUSD, H1)     129912254742671346 : Thực hiện trong  265 ms
 01 : 17 : 16.297     multwebclient (GBPJPY, M5)      129560567193673862 : Kết quả Reading \ Các chuyên gia \ multiweb.ex5 :: WRR_129912254742671346
 01 : 17 : 16,300     multiwebclient (GBPJPY, M5)       129560567193673862 : Chấn 16.568 byte trong phản ứng
 01 : 17 : 16,300     multiwebclient (GBPJPY, M5) GET https: //ya.ru 
01 : 17 : 16.300     multwebclient (GBPJPY, M5) Đã nhận được 3704 byte trong tiêu đề, 12775 byte trong tài liệu
 01 : 17 :16,715     multiwebclient (GBPJPY, M5)       129560567193673862 : Mã Kết quả 200 
01 : 17 : 16,715     multiwebclient (GBPJPY, M5)       129560567193673862 : Kết quả Reading \ Các chuyên gia \ multiweb.ex5 :: WRR_129912254742671347
 01 : 17 : 16,715     multiweb (EURUSD, H1)     129912254742671347 : Done trong  686 ms
 01 : 17 : 16,715     multiweb (EURUSD, H1) mã Kết quả từ  129912254742671347 : 200 , bây giờ nhàn rỗi
 01 :17 : 16,725     multiwebclient (GBPJPY, M5)       129560567193673862 : Chấn 45.236 byte trong phản ứng
 01 : 17 : 16,725     multiwebclient (GBPJPY, M5) GET https: //www.startpage.com/ 
01 : 17 : 16,725     multiwebclient (GBPJPY, M5) đã nhận 822 byte trong tiêu đề, 44325 byte trong tài liệu
 01 : 17 : 16.900     multwebclient (GBPJPY, M5)       129560567193673862 : Mã kết quả200 
01 : 17 : 16,900     multiweb (EURUSD, H1) mã Kết quả từ  129912254742671345 : 200 , bây giờ nhàn rỗi
 01 : 17 : 16,900     multiweb (EURUSD, H1)     129912254742671345 : Xong trong  873 ms
 01 : 17 : 16,900     multiwebclient (GBPJPY, M5)       129560567193673862 : Kết quả đọc \ Experts \ multiweb.ex5 :: WRR_129912254742671345
 01 : 17 : 16.903     multiwebclient (GBPJPY, M5)       129560567193673862: Nhận 13628 byte trong phản hồi
 01 : 17 : 16.903     multiwebclient (GBPJPY, M5) NHẬN https: //google.com/ 
01 : 17 : 16.903     multiwebclient (GBPJPY, M5) Nhận được 790 byte trong tiêu đề, 12747 byte trong tài liệu
 01 : 17 : 16.903     multiwebclient (GBPJPY, M5) >>> Async WebRequest worker [ 3 ] đã hoàn thành 3 nhiệm vụ trong  873 ms

Lưu ý rằng tổng thời gian thực hiện của tất cả các yêu cầu bằng với thời gian thực hiện của yêu cầu chậm nhất.

Nếu chúng ta đặt số lượng assistant thành một trong Manager EA, các yêu cầu sẽ được xử lý tuần tự.

Kết luận

Trong bài viết này, chúng ta đã xem xét một số lớp và EA được tạo sẵn để thực hiện các yêu cầu HTTP ở chế độ không chặn. Điều này cho phép lấy dữ liệu từ Internet theo một số luồng song song và tăng hiệu quả của EA, ngoài các yêu cầu HTTP, sẽ thực hiện các phép tính phân tích trong thời gian thực. Ngoài ra, thư viện này cũng có thể được sử dụng trong các chỉ số nơi WebRequest tiêu chuẩn bị cấm. Để thực hiện toàn bộ kiến ​​trúc, chúng ta đã phải sử dụng một loạt các tính năng MQL: chuyển các sự kiện của người dùng, tạo tài nguyên, mở cửa sổ động và chạy EA trên chúng.

Tại thời điểm viết bài, việc tạo các cửa sổ phụ trợ để khởi chạy assistant EA là tùy chọn duy nhất để xử lý các yêu cầu HTTP song song, nhưng MetaQuotes có kế hoạch phát triển các chương trình MQL nền đặc biệt. Thư mục MQL5 / Services đã được dành riêng cho các dịch vụ đó. Khi công nghệ này xuất hiện trong thiết bị đầu cuối, thư viện này có thể được cải thiện bằng cách thay thế các cửa sổ phụ trợ bằng các dịch vụ.

File đính kèm:

  • MQL5/Include/multiweb.mqh — library
  • MQL5/Experts/multiweb.mq5 — manager EA and assistant EA 
  • MQL5/Experts/multiwebclient.mq5 — demo client EA
  • MQL5/Include/fxsaber/Resource.mqh — auxiliary class for working with resources
  • MQL5/Include/fxsaber/ResourceData.mqh — auxiliary class for working with resources
  • MQL5/Include/fxsaber/Expert.mqh — auxiliary class for launching EAs
  • MQL5/Include/TypeToBytes.mqh — data conversion library
HotForex tặng thưởng 100% và miễn phí nạp rút tiền

BÌNH LUẬN

Vui lòng nhập bình luận của bạn
Vui lòng nhập tên của bạn ở đây

Website này sử dụng Akismet để hạn chế spam. Tìm hiểu bình luận của bạn được duyệt như thế nào.