Dịch vụ
Một dịch vụ là một chương trình MQL với một trình xử lý duy nhất là OnStart
và chỉ thị #property service
.
Hãy nhớ rằng sau khi biên dịch dịch vụ thành công, bạn cần tạo và cấu hình phiên bản của nó (một hoặc nhiều) bằng lệnh Add Service
trong menu ngữ cảnh của cửa sổ Navigator
.
Ví dụ về một dịch vụ, chúng ta sẽ giải quyết một vấn đề ứng dụng nhỏ thường gặp ở các nhà phát triển chương trình MQL. Nhiều người trong số họ thực hành việc gắn chương trình của mình với số tài khoản của người dùng. Điều này không nhất thiết liên quan đến sản phẩm trả phí mà có thể là phân phối giữa bạn bè và người quen để thu thập thống kê hoặc cài đặt thành công. Đồng thời, người dùng có thể đăng ký tài khoản demo ngoài tài khoản thực đang hoạt động. Thời gian tồn tại của các tài khoản này thường bị giới hạn, và do đó việc cập nhật liên kết cho chúng mỗi vài tuần khá bất tiện. Để làm điều này, bạn cần chỉnh sửa mã nguồn, biên dịch và gửi lại chương trình.
Thay vào đó, chúng ta có thể phát triển một dịch vụ sẽ đăng ký trong các biến toàn cục (hoặc tệp) các số tài khoản mà kết nối thành công đã được thực hiện từ terminal này.
Công nghệ gắn kết dựa trên mã hóa từng cặp (hoặc thay vào đó là băm) của các số tài khoản: tài khoản đăng nhập cũ và tài khoản đăng nhập mới. Tài khoản trước đó phải là tài khoản chính (mà liên kết có điều kiện được "cấp") để chữ ký chung của cặp mở rộng quyền sử dụng sản phẩm cho tài khoản mới. Khóa là một bí mật chỉ được biết trong các chương trình (giả định rằng tất cả đều được cung cấp ở dạng đóng, đã biên dịch). Kết quả của thao tác sẽ là một chuỗi ở định dạng Base64
. Việc thực hiện sử dụng các hàm API MQL5, một số trong đó chưa được nghiên cứu, cụ thể là lấy số tài khoản qua AccountInfoInteger
và hàm mã hóa CryptEncode
. Kết nối với máy chủ được kiểm tra bằng hàm TerminalInfoInteger
(xem Kiểm tra kết nối mạng).
Dịch vụ không cần biết tài khoản nào là chính, tài khoản nào là phụ. Nó chỉ cần "ký" các cặp tài khoản đăng nhập liên tiếp theo cách đặc biệt. Nhưng một chương trình ứng dụng cụ thể nên bổ sung quá trình kiểm tra "giấy phép" của nó: ngoài việc so sánh tài khoản hiện tại với tài khoản chính, bạn nên lặp lại thuật toán dịch vụ: tạo cặp [tài khoản chính; tài khoản hiện tại], tính toán chữ ký mã hóa cho nó, và kiểm tra xem nó có nằm trong các biến toàn cục không.
Sẽ chỉ có thể đánh cắp giấy phép như vậy bằng cách chuyển nó sang máy tính khác nếu bạn kết nối với cùng tài khoản ở chế độ giao dịch (không phải nhà đầu tư). Một người dùng không trung thực, tất nhiên, có thể tạo tài khoản demo cho người khác. Do đó, cần cải thiện bảo vệ. Trong triển khai hiện tại, biến toàn cục chỉ đơn giản được làm tạm thời, tức là bị xóa khi phiên terminal kết thúc, nhưng điều này không ngăn chặn việc sao chép có thể xảy ra.
Như các biện pháp bổ sung, có thể, ví dụ, mã hóa thời gian tạo của nó trong chữ ký và quy định quyền hết hạn mỗi ngày (hoặc với tần suất khác). Một lựa chọn khác là tạo một số ngẫu nhiên khi dịch vụ khởi động và thêm nó vào thông tin được ký cùng với số tài khoản. Số này chỉ được biết trong dịch vụ, nhưng nó có thể truyền nó đến các chương trình MQL quan tâm trên biểu đồ bằng hàm EventChartCustom
. Do đó, chữ ký sẽ tiếp tục có hiệu lực trong phiên bản terminal này cho đến khi kết thúc phiên. Mỗi phiên sẽ tạo và gửi một số ngẫu nhiên mới, nên nó sẽ không hoạt động cho các terminal khác. Cuối cùng, lựa chọn đơn giản và tiện lợi nhất có lẽ là thêm vào chữ ký thời gian bắt đầu hệ thống: (TimeLocal() - GetTickCount() / 1000)
hoặc biến thể của nó.
Trong số các loại chương trình MQL khác nhau, chỉ một số tiếp tục chạy giữa các lần chuyển đổi tài khoản và cho phép triển khai phương thức bảo vệ này. Vì cần bảo vệ các chương trình MQL thuộc bất kỳ loại nào một cách đồng nhất, bao gồm cả chỉ báo và Expert Advisors (được tải lại khi tài khoản thay đổi), nên giao nhiệm vụ này cho một dịch vụ là hợp lý. Sau đó, dịch vụ, chạy liên tục từ khi terminal được tải cho đến khi đóng, sẽ kiểm soát đăng nhập và tạo chữ ký ủy quyền.
Mã nguồn của dịch vụ được cung cấp trong tệp MQL5/Services/MQL5Book/p5/ServiceAccount.mq5
. Các tham số đầu vào chỉ định tài khoản chính và tiền tố của biến toàn cục nơi chữ ký sẽ được lưu trữ. Trong các chương trình thực tế, danh sách tài khoản chính nên được mã hóa cứng trong mã nguồn, và thay vì biến toàn cục, tốt hơn nên sử dụng tệp trong thư mục Common
để bao quát cả tester.
#property service
input long MasterAccount = 123456789;
input string Prefix = "!A_";
2
3
4
Hàm chính của dịch vụ thực hiện công việc như sau: trong một vòng lặp vô hạn với khoảng dừng 1 giây, chúng ta theo dõi thay đổi tài khoản và lưu số cuối cùng, tạo chữ ký cho cặp, và ghi nó vào biến toàn cục. Chữ ký được tạo bởi hàm Cipher
.
void OnStart()
{
static long account = 0; // đăng nhập trước đó
for(; !IsStopped(); )
{
// yêu cầu kết nối, đăng nhập thành công và toàn quyền truy cập (không phải nhà đầu tư)
const bool c = TerminalInfoInteger(TERMINAL_CONNECTED)
&& AccountInfoInteger(ACCOUNT_TRADE_ALLOWED);
const long a = c ? AccountInfoInteger(ACCOUNT_LOGIN) : 0;
if(account != a) // tài khoản thay đổi
{
if(a != 0) // tài khoản hiện tại
{
if(account != 0) // tài khoản trước đó
{
// chuyển giao quyền từ cái này sang cái kia
const string signature = Cipher(account, a);
PrintFormat("Account %I64d registered by %I64d: %s",
a, account, signature);
// lưu bản ghi về kết nối của các tài khoản
if(StringLen(signature) > 0)
{
GlobalVariableTemp(Prefix + signature);
GlobalVariableSet(Prefix + signature, account);
}
}
else // tài khoản đầu tiên được ủy quyền, giờ chờ cái thứ hai
{
PrintFormat("New account %I64d detected", a);
}
// ghi nhớ tài khoản hoạt động cuối cùng
account = a;
}
}
Sleep(1000);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Hàm Cipher
sử dụng một liên kết đặc biệt ByteOverlay2
để biểu diễn cặp số tài khoản (loại long
) dưới dạng mảng byte, được truyền để mã hóa trong CryptEncode
(phương thức mã hóa CRYPT_DES được chọn ở đây, nhưng có thể thay bằng CRYPT_AES128, CRYPT_AES256 hoặc chỉ băm CRYPT_HASH_SHA256 (với bí mật làm "muối"), nếu không cần khôi phục thông tin từ "chữ ký").
template<typename T>
union ByteOverlay2
{
T values[2];
uchar bytes[sizeof(T) * 2];
ByteOverlay2(const T v1, const T v2) { values[0] = v1; values[1] = v2; }
};
string Cipher(const long data1, const long data2)
{
// TODO: thay bí mật bằng cụm mật khẩu của bạn
// TODO: phương thức CRYPT_AES128/CRYPT_AES256 yêu cầu mảng 16/32 byte
const static uchar secret[] = {'S', 'E', 'C', 'R', 'E', 'T', '0'};
ByteOverlay2<long> bo(data1, data2);
uchar result[];
if(CryptEncode(CRYPT_DES, bo.bytes, secret, result) > 0)
{
uchar dummy[], text[];
if(CryptEncode(CRYPT_BASE64, result, dummy, text) > 0)
{
return CharArrayToString(text);
}
}
return NULL;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Sau đó, bất kỳ chương trình nào trong terminal có thể kiểm tra xem có "giấy phép" cho tài khoản hiện tại trong biến toàn cục không. Điều này được thực hiện bằng các hàm CheckAccounts
và IsCurrentAccountAuthorizedByMaster
. Chúng được hiển thị trong dịch vụ chỉ để minh họa.
Hàm CheckAccounts
thực hiện kiểm tra trên tất cả tài khoản chính được mã hóa cứng để tìm những tài khoản khớp với tài khoản hiện tại.
bool CheckAccounts()
{
const long accounts[] = {MasterAccount}; // TODO: điền mảng bằng hằng số
for(int i = 0; i < ArraySize(accounts); ++i)
{
if(IsCurrentAccountAuthorizedByMaster(accounts[i])) return true;
}
return false;
}
2
3
4
5
6
7
8
9
IsCurrentAccountAuthorizedByMaster
nhận số của một tài khoản chính làm tham số, tái tạo "chữ ký" cho nó trong cặp với tài khoản hiện tại, và phân tích sự khớp.
bool IsCurrentAccountAuthorizedByMaster(const long data)
{
const long a = AccountInfoInteger(ACCOUNT_LOGIN);
if(a == data) return true; // khớp trực tiếp
const string s = Cipher(data, a); // tính toán lại "chữ ký"
if(a != 0 && GlobalVariableGet(Prefix + s) == a)
{
Print("Sub-License is active: ", s);
return true;
}
return false;
}
2
3
4
5
6
7
8
9
10
11
12
Giả sử các chương trình được phép chạy trên tài khoản 123456789 và nó hiện đang hoạt động. Khi khởi động, dịch vụ sẽ phản hồi với một mục nhật ký:
New account 123456789 detected
Nếu sau đó chúng ta thay đổi số tài khoản, ví dụ, thành 5555555, chúng ta nhận được chữ ký sau:
Account 5555555 registered by 123456789: jdVKxUswBiNlZzDAnV3yxw==
Nếu chúng ta dừng và khởi động lại dịch vụ, chúng ta sẽ thấy việc xác minh tài khoản 5555555 hoạt động (gọi hàm CheckAccounts
được nhúng để minh họa ở đầu OnStart
).
Sub-License is active: jdVKxUswBiNlZzDAnV3yxw==
Account 123456789 registered by 5555555: ZWcwwJ1d8seN1UrFSzAGIw==
2
Giấy phép đã hoạt động cho tài khoản mới. Nếu bạn chuyển lại, một "pass" sẽ được tạo từ tài khoản hiện tại sang tài khoản trước đó (đây là hệ quả của việc dịch vụ không "biết" tài khoản nào là chính và tài khoản nào là tạm thời, và "chữ ký" như vậy có lẽ không cần thiết trong các chương trình).
Để ủy quyền gián tiếp cho một tài khoản mới, bạn sẽ cần đăng nhập lại vào tài khoản chính và sau đó mới chuyển sang tài khoản mới: điều này sẽ tạo ra một biến toàn cục khác với cặp mã hóa [tài khoản chính; tài khoản mới].
Phiên bản này của dịch vụ không kiểm tra xem tài khoản chính có thực và tài khoản phụ thuộc có phải là demo hay không. Mỗi hạn chế này có thể được thêm vào.