... констатируя очевидное, если вы хотите сделать вышеописанное в советнике, а не в скрипте [...].
Переработанная версия, которую можно использовать для создания советника, а не скрипта, просто убрав комментарий #define COMPILE_AS_EA. Тогда код будет иметь OnTimer (и OnTick) вместо OnStart.
Единственным другим изменением является то, что код теперь переводит сокеты в неблокирующий режим. Он по-прежнему опрашивает наличие новых событий сокета с помощью select(), но теперь он избегает опасности вхождения в состояние блокировки, если select() окажется ошибочным.
#property strict// ---------------------------------------------------------------------// This code can either create an EA or a script. To create an EA,// remove the commenting-out of the line below. The code will then// have OnTimer() and OnTick() instead of OnStart()// ---------------------------------------------------------------------// #define COMPILE_AS_EA// ---------------------------------------------------------------------// If we are compiling as a script, then we want the script// to display the inputs page// ---------------------------------------------------------------------#ifndef COMPILE_AS_EA
#property show_inputs
#endif
// ---------------------------------------------------------------------// User-configurable parameters// ---------------------------------------------------------------------inputint PortNumber = 51234; // TCP/IP port number// ---------------------------------------------------------------------// Constants// ---------------------------------------------------------------------// Size of temporary buffer used to read from client sockets#define SOCKET_READ_BUFFER_SIZE 10000// Defines the frequency of the main-loop processing, in milliseconds.// This is either the duration of the sleep in OnStart(), if compiled// as a script, or the frequency which is given to EventSetMillisecondTimer()// if compiled as an EA#define SLEEP_MILLISECONDS 10// ---------------------------------------------------------------------// Forward definitions of classes// ---------------------------------------------------------------------// Wrapper around a connected client socketclass Connection;
// ---------------------------------------------------------------------// Winsock structure definitions and DLL imports// ---------------------------------------------------------------------struct sockaddr_in {
short af_family;
short port;
int addr;
int dummy1;
int dummy2;
};
struct timeval {
int secs;
int usecs;
};
struct fd_set {
int count;
int single_socket;
int dummy[63];
};
#import "Ws2_32.dll"int socket(int, int, int);
int bind(int, sockaddr_in&, int);
int htons(int);
int listen(int, int);
int accept(int, int, int);
int closesocket(int);
int select(int, fd_set&, int, int, timeval&);
int recv(int, uchar&[], int, int);
int ioctlsocket(int, uint, uint&);
int WSAGetLastError();
#import
// ---------------------------------------------------------------------// Global variables// ---------------------------------------------------------------------// Flags whether OnInit was successfulbool SuccessfulInit = false;
// Handle of main listening server socketint ServerSocket;
// List of currently connected clients
Connection * Clients[];
// For EA compilation only, we track whether we have // successfully created a timer#ifdef COMPILE_AS_EA
bool CreatedTimer = false;
#endif
// ---------------------------------------------------------------------// Initialisation - create listening socket// ---------------------------------------------------------------------voidOnInit()
{
SuccessfulInit = false;
if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Print("Requires \'Allow DLL imports\'");return;}
// (Don't need to call WSAStartup because MT4 must have done this)// Create the main server socket
ServerSocket = socket(2/* AF_INET */, 1/* SOCK_STREAM */, 6/* IPPROTO_TCP */);
if (ServerSocket == -1) {Print("ERROR " , WSAGetLastError() , " in socket creation");return;}
// Put the socket into non-blocking modeuint nbmode = 1;
if (ioctlsocket(ServerSocket, 0x8004667E/* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on server socket");return;}
// Bind the socket to the specified port number. In this example,// we only accept connections from localhost
sockaddr_in service;
service.af_family = 2/* AF_INET */;
service.addr = 0x100007F; // equivalent to inet_addr("127.0.0.1")
service.port = (short)htons(PortNumber);
if (bind(ServerSocket, service, 16/* sizeof(service) */) == -1) {Print("ERROR " , WSAGetLastError() , " in socket bind");return;}
// Put the socket into listening modeif (listen(ServerSocket, 10) == -1) {Print("ERROR " , WSAGetLastError() , " in socket listen");return;}
// Flag that we've successfully initialised
SuccessfulInit = true;
// If we're operating as an EA rather than a script, then try setting up a timer#ifdef COMPILE_AS_EA
CreateTimer();
#endif
}
// ---------------------------------------------------------------------// Variation between EA and script, depending on COMPILE_AS_EA above.// Both versions simply call MainLoop() to handle the socket activity.// The script version simply does so from a loop in OnStart().// The EA version sets up a timer, and then calls MainLoop from OnTimer()// ---------------------------------------------------------------------#ifdef COMPILE_AS_EA
// .........................................................// EA version. Simply calls MainLoop() from a timer.// The timer has previously been created in OnInit(), // though we also check this in OnTick()...voidOnTimer()
{
MainLoop();
}
// The function which creates the timer if it hasn't yet been set upvoid CreateTimer()
{
if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer(SLEEP_MILLISECONDS);
}
// Timer creation can sometimes fail in OnInit(), at least// up until MT4 build 970, if MT4 is starting up with the EA already// attached to a chart. Therefore, as a fallback, we also // handle OnTick(), and thus eventually set up the timer in// the EA's first tick if we failed to set it up in OnInit()voidOnTick()
{
CreateTimer();
}
#else
// .........................................................// Script version. Simply calls MainLoop() repeatedly// until the script is removed, with a small sleep between callsvoidOnStart()
{
while (!IsStopped()) {
MainLoop();
Sleep(SLEEP_MILLISECONDS);
}
}
#endif
// ---------------------------------------------------------------------// Main processing loop which handles new incoming connections, and reads// data from existing connections.// Can either be called from OnStart() in a script, on a continuous loop // until IsStopped() is true; or from OnTimer() in an EA.// ---------------------------------------------------------------------void MainLoop()
{
// Do nothing if init was unsuccessfulif (!SuccessfulInit) return;
// .........................................................// Do we have a new pending connection on the server socket?
timeval waitfor;
waitfor.secs = 0;
waitfor.usecs = 0;
fd_set PollServerSocket;
PollServerSocket.count = 1;
PollServerSocket.single_socket = ServerSocket;
int selres = select(0, PollServerSocket, 0, 0, waitfor);
if (selres > 0) {
Print("New incoming connection...");
int NewClientSocket = accept(ServerSocket, 0, 0);
if (NewClientSocket == -1) {
if (WSAGetLastError() == 10035/* WSAEWOULDBLOCK */) {
// Blocking warning; ignore
} else {
Print("ERROR " , WSAGetLastError() , " in socket accept");
}
} else {
Print("...accepted");
int ctarr = ArraySize(Clients);
ArrayResize(Clients, ctarr + 1);
Clients[ctarr] = new Connection(NewClientSocket);
Print("Got connection to client #", Clients[ctarr].GetID());
}
}
// .........................................................// Process any incoming data from client connections// (including any which have just been accepted, above)int ctarr = ArraySize(Clients);
for (int i = ctarr - 1; i >= 0; i--) {
// Return value from ReadAnyPendingData() is true// if the socket still seems to be alive; false if // the connection seems to have been closed, and should be discardedif (Clients[i].ReadAnyPendingData()) {
// Socket still seems to be alive
} else {
// Socket appears to be dead. Delete, and remove from listPrint("Lost connection to client #", Clients[i].GetID());
delete Clients[i];
for (int j = i + 1; j < ctarr; j++) {
Clients[j - 1] = Clients[j];
}
ctarr--;
ArrayResize(Clients, ctarr);
}
}
}
// ---------------------------------------------------------------------// Termination - clean up sockets// ---------------------------------------------------------------------voidOnDeinit(constint reason)
{
closesocket(ServerSocket);
for (int i = 0; i < ArraySize(Clients); i++) {
delete Clients[i];
}
ArrayResize(Clients, 0);
#ifdef COMPILE_AS_EA
EventKillTimer();
CreatedTimer = false;
#endif
}
// ---------------------------------------------------------------------// Simple wrapper around each connected client socket// ---------------------------------------------------------------------class Connection {
private:
// Client socket handleint mSocket;
// Temporary buffer used to handle incoming datauchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
// Stored-up data, waiting for a \r character string mPendingData;
public:
Connection(int ClientSocket);
~Connection();
string GetID() {returnIntegerToString(mSocket);}
bool ReadAnyPendingData();
void ProcessIncomingMessage(string strMessage);
};
// Constructor, called with the handle of a newly accepted socket
Connection::Connection(int ClientSocket)
{
mPendingData = "";
mSocket = ClientSocket;
// Put the client socket into non-blocking modeuint nbmode = 1;
if (ioctlsocket(mSocket, 0x8004667E/* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on client socket");}
}
// Destructor. Simply close the client socket
Connection::~Connection()
{
closesocket(mSocket);
}
// Called repeatedly on a timer, to check whether any// data is available on this client connection. Returns true if the // client still seems to be connected (*not* if there's new data); // returns false if the connection seems to be dead. bool Connection::ReadAnyPendingData()
{
// Check the client socket for data-readability
timeval waitfor;
waitfor.secs = 0;
waitfor.usecs = 0;
fd_set PollClientSocket;
PollClientSocket.count = 1;
PollClientSocket.single_socket = mSocket;
int selres = select(0, PollClientSocket, 0, 0, waitfor);
if (selres > 0) {
// Winsock says that there is data waiting to be read on this socketint res = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0);
if (res > 0) {
// Convert the buffer to a string, and add it to any pending// data which we already have on this connectionstring strIncoming = CharArrayToString(mTempBuffer, 0, res);
mPendingData += strIncoming;
// Do we have a complete message (or more than one) ending in \r?int idxTerm = StringFind(mPendingData, "\r");
while (idxTerm >= 0) {
if (idxTerm > 0) {
string strMsg = StringSubstr(mPendingData, 0, idxTerm);
// Strip out any \n characters lingering from \r\n combosStringReplace(strMsg, "\n", "");
// Print the \r-terminated message in the log
ProcessIncomingMessage(strMsg);
}
// Keep looping until we have extracted all the \r delimited // messages, and leave any residue in the pending data
mPendingData = StringSubstr(mPendingData, idxTerm + 1);
idxTerm = StringFind(mPendingData, "\r");
}
returntrue;
} elseif (WSAGetLastError() == 10035/* WSAEWOULDBLOCK */) {
// Would block; not an errorreturntrue;
} else {
// recv() failed. Assume socket is deadreturnfalse;
}
} elseif (selres == -1) {
// Assume socket is deadreturnfalse;
} else {
// No pending datareturntrue;
}
}
// Can override this with whatever you want to do with incoming messages:// handling trading commands, send data back down the socket etc etc etcvoid Connection::ProcessIncomingMessage(string strMessage)
{
Print("#" , GetID() , ": " , strMessage);
}
Я уверен, что все с нетерпением ждут обновления по этому вопросу...
В контексте эксперта - который теперь используется по умолчанию в коде - оказывается, что можно использовать WSAAsyncSelect() для событийно-управляемой обработки активности сокета. Это часто позволяет снизить задержку между отправкой и получением сообщения ниже уровня миллисекунд. Точная скорость зависит от того, что еще делает MT4, но в среднем это намного быстрее, и никогда не хуже, чем просто использование таймера.
Невозможно заставить WSAAsyncSelect() вызвать OnTimer() или OnTick() в советнике. Например, можно сказать WSAAsyncSelect(), чтобы она отправляла сообщения WM_TIMER, но MT4 игнорирует их, потому что идентификатор таймера wparam из WSAAsyncSelect() не совпадает с идентификатором таймера, который она ожидает от EventSetMillisecondTimer().
Однако, указание WSAAsyncSelect() посылать WM_KEYDOWN успешно запускает OnChartEvent() в советнике. См. комментарии в коде для получения подробной информации о том, как работает это дополнение.
#property strict// ---------------------------------------------------------------------// This code can either create an EA or a script. To create a script,// comment out the line below. The code will then have OnTimer() // and OnTick() instead of OnStart()// ---------------------------------------------------------------------#define COMPILE_AS_EA
// ---------------------------------------------------------------------// If we are compiling as a script, then we want the script// to display the inputs page// ---------------------------------------------------------------------#ifndef COMPILE_AS_EA
#property show_inputs
#endif
// ---------------------------------------------------------------------// User-configurable parameters// ---------------------------------------------------------------------inputint PortNumber = 51234; // TCP/IP port number// ---------------------------------------------------------------------// Constants// ---------------------------------------------------------------------// Size of temporary buffer used to read from client sockets#define SOCKET_READ_BUFFER_SIZE 10000// Defines the frequency of the main-loop processing, in milliseconds.// This is either the duration of the sleep in OnStart(), if compiled// as a script, or the frequency which is given to EventSetMillisecondTimer()// if compiled as an EA#define SLEEP_MILLISECONDS 10// ---------------------------------------------------------------------// Forward definitions of classes// ---------------------------------------------------------------------// Wrapper around a connected client socketclass Connection;
// ---------------------------------------------------------------------// Winsock structure definitions and DLL imports// ---------------------------------------------------------------------struct sockaddr_in {
short af_family;
short port;
int addr;
int dummy1;
int dummy2;
};
struct timeval {
int secs;
int usecs;
};
struct fd_set {
int count;
int single_socket;
int dummy[63];
};
#import "Ws2_32.dll"int socket(int, int, int);
int bind(int, sockaddr_in&, int);
int htons(int);
int listen(int, int);
int accept(int, int, int);
int closesocket(int);
int select(int, fd_set&, int, int, timeval&);
int recv(int, uchar&[], int, int);
int ioctlsocket(int, uint, uint&);
int WSAGetLastError();
int WSAAsyncSelect(int, int, uint, int);
#import
// ---------------------------------------------------------------------// Global variables// ---------------------------------------------------------------------// Flags whether OnInit was successfulbool SuccessfulInit = false;
// Handle of main listening server socketint ServerSocket;
// List of currently connected clients
Connection * Clients[];
// For EA compilation only, we track whether we have // successfully created a timer#ifdef COMPILE_AS_EA
bool CreatedTimer = false;
#endif
// ---------------------------------------------------------------------// Initialisation - create listening socket// ---------------------------------------------------------------------voidOnInit()
{
SuccessfulInit = false;
if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Print("Requires \'Allow DLL imports\'");return;}
// (Don't need to call WSAStartup because MT4 must have done this)// Create the main server socket
ServerSocket = socket(2/* AF_INET */, 1/* SOCK_STREAM */, 6/* IPPROTO_TCP */);
if (ServerSocket == -1) {Print("ERROR " , WSAGetLastError() , " in socket creation");return;}
// Put the socket into non-blocking modeuint nbmode = 1;
if (ioctlsocket(ServerSocket, 0x8004667E/* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on server socket");return;}
// Bind the socket to the specified port number. In this example,// we only accept connections from localhost
sockaddr_in service;
service.af_family = 2/* AF_INET */;
service.addr = 0x100007F; // equivalent to inet_addr("127.0.0.1")
service.port = (short)htons(PortNumber);
if (bind(ServerSocket, service, 16/* sizeof(service) */) == -1) {Print("ERROR " , WSAGetLastError() , " in socket bind");return;}
// Put the socket into listening modeif (listen(ServerSocket, 10) == -1) {Print("ERROR " , WSAGetLastError() , " in socket listen");return;}
#ifdef COMPILE_AS_EA
// In the context of an EA, we can improve the latency even further by making the // processing event-driven, rather than just on a timer. If we use// WSAAsyncSelect() to tell Windows to send a WM_KEYDOWN whenever there's socket// activity, then this will fire the EA's OnChartEvent. We can thus collect// socket events even faster than via the timed check, which becomes a back-up.// Note that WSAAsyncSelect() on a server socket also automatically applies // to all client sockets created from it by accept(), and also that// WSAAsyncSelect() puts the socket into non-blocking mode, duplicating what// we have already done above using ioctlsocket()if (WSAAsyncSelect(ServerSocket, (int)ChartGetInteger(0, CHART_WINDOW_HANDLE), 0x100/* WM_KEYDOWN */, 0xFF/* All events */) != 0) {
Print("ERROR " , WSAGetLastError() , " in async select");
}
#endif
// Flag that we've successfully initialised
SuccessfulInit = true;
// If we're operating as an EA rather than a script, then try setting up a timer#ifdef COMPILE_AS_EA
CreateTimer();
#endif
}
// ---------------------------------------------------------------------// Variation between EA and script, depending on COMPILE_AS_EA above.// Both versions simply call MainLoop() to handle the socket activity.// The script version simply does so from a loop in OnStart().// The EA version sets up a timer, and then calls MainLoop from OnTimer().// It also has event-driven handling of socket activity by getting// Windows to simulate keystrokes whenever a socket event happens.// This is even faster than relying on the timer.// ---------------------------------------------------------------------#ifdef COMPILE_AS_EA
// .........................................................// EA version. Simply calls MainLoop() from a timer.// The timer has previously been created in OnInit(), // though we also check this in OnTick()...voidOnTimer()
{
MainLoop();
}
// The function which creates the timer if it hasn't yet been set upvoid CreateTimer()
{
if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer(SLEEP_MILLISECONDS);
}
// Timer creation can sometimes fail in OnInit(), at least// up until MT4 build 970, if MT4 is starting up with the EA already// attached to a chart. Therefore, as a fallback, we also // handle OnTick(), and thus eventually set up the timer in// the EA's first tick if we failed to set it up in OnInit()voidOnTick()
{
CreateTimer();
}
// In an EA, the use of WSAAsyncSelect() above means that Windows will fire a key-down // message whenever there is socket activity. We can then respond to KEYDOWN events// in MQL4 as a way of knowing that there is socket activity to be dealt with.voidOnChartEvent(constint id, constlong& lparam, constdouble& dparam, conststring& sparam)
{
if (id == CHARTEVENT_KEYDOWN) {
// The lparam will be the socket handle, and the dparam will be the type// of socket event (e.g. dparam = 1 = FD_READ). The fastest option would be// to use the socket handle in lparam to identify the specific socket// which requires action, but we'll take the slightly less efficient but simpler// route where we just do all our full socket handling in response to an event.// Similarly, we won't bother to distinguish between real keypresses and // the pseudo-keypresses from WSAAyncSelect().
MainLoop();
}
}
#else
// .........................................................// Script version. Simply calls MainLoop() repeatedly// until the script is removed, with a small sleep between callsvoidOnStart()
{
while (!IsStopped()) {
MainLoop();
Sleep(SLEEP_MILLISECONDS);
}
}
#endif
// ---------------------------------------------------------------------// Main processing loop which handles new incoming connections, and reads// data from existing connections.// Can either be called from OnStart() in a script, on a continuous loop // until IsStopped() is true; or from OnTimer() in an EA.// ---------------------------------------------------------------------void MainLoop()
{
// Do nothing if init was unsuccessfulif (!SuccessfulInit) return;
// .........................................................// Do we have a new pending connection on the server socket?
timeval waitfor;
waitfor.secs = 0;
waitfor.usecs = 0;
fd_set PollServerSocket;
PollServerSocket.count = 1;
PollServerSocket.single_socket = ServerSocket;
int selres = select(0, PollServerSocket, 0, 0, waitfor);
if (selres > 0) {
Print("New incoming connection...");
int NewClientSocket = accept(ServerSocket, 0, 0);
if (NewClientSocket == -1) {
if (WSAGetLastError() == 10035/* WSAEWOULDBLOCK */) {
// Blocking warning; ignore
} else {
Print("ERROR " , WSAGetLastError() , " in socket accept");
}
} else {
Print("...accepted");
int ctarr = ArraySize(Clients);
ArrayResize(Clients, ctarr + 1);
Clients[ctarr] = new Connection(NewClientSocket);
Print("Got connection to client #", Clients[ctarr].GetID());
}
}
// .........................................................// Process any incoming data from client connections// (including any which have just been accepted, above)int ctarr = ArraySize(Clients);
for (int i = ctarr - 1; i >= 0; i--) {
// Return value from ReadAnyPendingData() is true// if the socket still seems to be alive; false if // the connection seems to have been closed, and should be discardedif (Clients[i].ReadAnyPendingData()) {
// Socket still seems to be alive
} else {
// Socket appears to be dead. Delete, and remove from listPrint("Lost connection to client #", Clients[i].GetID());
delete Clients[i];
for (int j = i + 1; j < ctarr; j++) {
Clients[j - 1] = Clients[j];
}
ctarr--;
ArrayResize(Clients, ctarr);
}
}
}
// ---------------------------------------------------------------------// Termination - clean up sockets// ---------------------------------------------------------------------voidOnDeinit(constint reason)
{
closesocket(ServerSocket);
for (int i = 0; i < ArraySize(Clients); i++) {
delete Clients[i];
}
ArrayResize(Clients, 0);
#ifdef COMPILE_AS_EA
EventKillTimer();
CreatedTimer = false;
#endif
}
// ---------------------------------------------------------------------// Simple wrapper around each connected client socket// ---------------------------------------------------------------------class Connection {
private:
// Client socket handleint mSocket;
// Temporary buffer used to handle incoming datauchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
// Stored-up data, waiting for a \r character string mPendingData;
public:
Connection(int ClientSocket);
~Connection();
string GetID() {returnIntegerToString(mSocket);}
bool ReadAnyPendingData();
void ProcessIncomingMessage(string strMessage);
};
// Constructor, called with the handle of a newly accepted socket
Connection::Connection(int ClientSocket)
{
mPendingData = "";
mSocket = ClientSocket;
// Put the client socket into non-blocking modeuint nbmode = 1;
if (ioctlsocket(mSocket, 0x8004667E/* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on client socket");}
}
// Destructor. Simply close the client socket
Connection::~Connection()
{
closesocket(mSocket);
}
// Called repeatedly on a timer, to check whether any// data is available on this client connection. Returns true if the // client still seems to be connected (*not* if there's new data); // returns false if the connection seems to be dead. bool Connection::ReadAnyPendingData()
{
// Check the client socket for data-readability
timeval waitfor;
waitfor.secs = 0;
waitfor.usecs = 0;
fd_set PollClientSocket;
PollClientSocket.count = 1;
PollClientSocket.single_socket = mSocket;
int selres = select(0, PollClientSocket, 0, 0, waitfor);
if (selres > 0) {
// Winsock says that there is data waiting to be read on this socketint res = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0);
if (res > 0) {
// Convert the buffer to a string, and add it to any pending// data which we already have on this connectionstring strIncoming = CharArrayToString(mTempBuffer, 0, res);
mPendingData += strIncoming;
// Do we have a complete message (or more than one) ending in \r?int idxTerm = StringFind(mPendingData, "\r");
while (idxTerm >= 0) {
if (idxTerm > 0) {
string strMsg = StringSubstr(mPendingData, 0, idxTerm);
// Strip out any \n characters lingering from \r\n combosStringReplace(strMsg, "\n", "");
// Print the \r-terminated message in the log
ProcessIncomingMessage(strMsg);
}
// Keep looping until we have extracted all the \r delimited // messages, and leave any residue in the pending data
mPendingData = StringSubstr(mPendingData, idxTerm + 1);
idxTerm = StringFind(mPendingData, "\r");
}
returntrue;
} elseif (WSAGetLastError() == 10035/* WSAEWOULDBLOCK */) {
// Would block; not an errorreturntrue;
} else {
// recv() failed. Assume socket is deadreturnfalse;
}
} elseif (selres == -1) {
// Assume socket is deadreturnfalse;
} else {
// No pending datareturntrue;
}
}
// Can override this with whatever you want to do with incoming messages:// handling trading commands, send data back down the socket etc etc etcvoid Connection::ProcessIncomingMessage(string strMessage)
{
Print("#" , GetID() , ": " , strMessage);
}
Лол. Потрясающе. Поздравляю. Я и не думал, что на MQL можно реализовать такой функционал.
Мой инстинкт - всегда делать серверные сокеты с использованием многопоточности, но я не могу придумать причину, по которой этот код не был бы жизнеспособным и безопасным для реального использования.
В последней версии, приведенной выше, есть одна вещь, которая не является идеальной. Вместо того чтобы публиковать еще один большой блок кода, я опишу его:
Использование WSASyncSelect() для более быстрой обработки событий зависит от наличия хэндла окна графика.
Но попытка использовать CHART_WINDOW_HANDLE приведет к ошибке в OnInit(), если MT4 запускается с советником, уже и ранее прикрепленным к графику (как и уже рассмотренный факт, что EventSetMillisecondTimer может не сработать в OnInit).
Поэтому, если вы перезапустите MT4 с уже прикрепленным и запущенным советником, вызов WSAAsyncSelect будет неудачным, и вы получите только обработку активности сокета на основе таймера, а не более быструю обработку на основе событий.
Это небольшое изменение в коде, но я не хочу постить еще один большой блок в этот форум, когда это интересно только вам.
Вместо того, чтобы публиковать еще один большой блок кода [...]
На второй размышления...
Еще одна версия со следующими изменениями:
Работает на МТ5 (как 32-битной, так и 64-битной), а также на МТ4.
Обрабатывает тот факт, что CHART_WINDOW_HANDLE может дать сбой в OnInit
Ускоряет обработку управляемых событиями уведомлений через OnChartEvent, проверяя только конкретный сокет, сгенерировавший событие, а не запуская весь MainLoop().
При желании разрешает подключения с любого компьютера, а не только с локального хоста, переключается с помощью параметра во входах.
// *********************************************************************// Example of non-blocking server sockets, allowing connections// from multiple concurrent clients. This example waits for // each \r-terminated line from a client (e.g. Telnet), and prints it // to the Experts log. The behaviour can be extended into something// more realistic by changing Connection::ProcessIncomingMessage().//// Works on both MT4 and MT5 (32-bit and 64-bit). But see the // notes to socketselect3264() about the cheat which is used// on 64-bit MT5.// *********************************************************************#property strict// ---------------------------------------------------------------------// This code can either create an EA or a script. To create a script,// comment out the line below. The code will then have OnTimer() // and OnTick() instead of OnStart()// ---------------------------------------------------------------------#define COMPILE_AS_EA
// ---------------------------------------------------------------------// If we are compiling as a script, then we want the script// to display the inputs page// ---------------------------------------------------------------------#ifndef COMPILE_AS_EA
#property show_inputs
#endif
// ---------------------------------------------------------------------// User-configurable parameters// ---------------------------------------------------------------------// Purely cosmetic; causes MT4 to display the comments below// as the options for the input, instead of using a boolean true/falseenum AllowedAddressesEnum {
aaeLocal = 0 , // localhost only (127.0.0.1)
aaeAny = 1// All IP addresses
};
// User inputsinputint PortNumber = 51234 ; // TCP/IP port numberinput AllowedAddressesEnum AcceptFrom = aaeLocal; // Accept connections from// ---------------------------------------------------------------------// Constants// ---------------------------------------------------------------------// Size of temporary buffer used to read from client sockets#define SOCKET_READ_BUFFER_SIZE 10000// Defines the frequency of the main-loop processing, in milliseconds.// This is either the duration of the sleep in OnStart(), if compiled// as a script, or the frequency which is given to EventSetMillisecondTimer()// if compiled as an EA. (In the context of an EA, the use of additional// event-driven handling means that the timer is only a fallback, and// this value could be set to a larger value, just acting as a sweep-up)#define SLEEP_MILLISECONDS 10// ---------------------------------------------------------------------// Forward definitions of classes// ---------------------------------------------------------------------// Wrapper around a connected client socketclass Connection;
// ---------------------------------------------------------------------// Winsock structure definitions and DLL imports// ---------------------------------------------------------------------struct sockaddr_in {
short af_family;
short port;
int addr;
int dummy1;
int dummy2;
};
struct timeval {
int secs;
int usecs;
};
struct fd_set {
int count;
int single_socket;
int dummy[ 63 ];
};
struct fd_set64 {
long count;
long single_socket;
long dummy[ 63 ];
};
#import "Ws2_32.dll"int socket( int , int , int );
int bind( int , sockaddr_in&, int );
int htons( int );
int listen( int , int );
int accept( int , int , int );
int closesocket( int );
int select( int , fd_set&, int , int , timeval&);
int select( int , fd_set64&, int , int , timeval&); // See notes to socketselect3264() belowint recv( int , uchar &[], int , int );
int ioctlsocket( int , uint , uint &);
int WSAGetLastError();
int WSAAsyncSelect( int , int , uint , int );
#import
// ---------------------------------------------------------------------// Global variables// ---------------------------------------------------------------------// Flags whether OnInit was successfulbool SuccessfulInit = false ;
// Handle of main listening server socketint ServerSocket;
// List of currently connected clients
Connection * Clients[];
#ifdef COMPILE_AS_EA
// For EA compilation only, we track whether we have // successfully created a timerbool CreatedTimer = false ;
// For EA compilation only, we track whether we have // done WSAAsyncSelect(), which can't reliably done in OnInit()// because a chart handle isn't necessarily availablebool DoneAsyncSelect = false ;
#endif
// ---------------------------------------------------------------------// Initialisation - create listening socket// ---------------------------------------------------------------------voidOnInit ()
{
SuccessfulInit = false ;
if (! TerminalInfoInteger ( TERMINAL_DLLS_ALLOWED )) { Print ( "Requires \'Allow DLL imports\'" ); return ;}
// (Don't need to call WSAStartup because MT4 must have done this)// Create the main server socket
ServerSocket = socket( 2/* AF_INET */ , 1/* SOCK_STREAM */ , 6/* IPPROTO_TCP */ );
if (ServerSocket == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket creation" ); return ;}
// Put the socket into non-blocking modeuint nbmode = 1 ;
if (ioctlsocket(ServerSocket, 0x8004667E/* FIONBIO */ , nbmode) != 0 ) { Print ( "ERROR in setting non-blocking mode on server socket" ); return ;}
// Bind the socket to the specified port number. In this example,// we only accept connections from localhost
sockaddr_in service;
service.af_family = 2/* AF_INET */ ;
service.addr = (AcceptFrom == aaeAny ? 0/* INADDR_ANY */ : 0x100007F/* 127.0.0.1 */ );
service.port = ( short )htons(PortNumber);
if (bind(ServerSocket, service, 16/* sizeof(service) */ ) == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket bind" ); return ;}
// Put the socket into listening modeif (listen(ServerSocket, 10 ) == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket listen" ); return ;}
#ifdef COMPILE_AS_EA
// If we're operating as an EA, set up event-driven handling of the sockets// as described below. (Also called from OnTick() because this requires a // window handle, and the set-up will fail here if MT4 is starting up// with the EA already attached to a chart.)
SetupAsyncSelect();
#endif
// Flag that we've successfully initialised
SuccessfulInit = true ;
// If we're operating as an EA rather than a script, then try setting up a timer#ifdef COMPILE_AS_EA
CreateTimer();
#endif
}
// ---------------------------------------------------------------------// Variation between EA and script, depending on COMPILE_AS_EA above.// The script version simply calls MainLoop() from a loop in OnStart().// The EA version sets up a timer, and then calls MainLoop from OnTimer().// It also has event-driven handling of socket activity by getting// Windows to simulate keystrokes whenever a socket event happens.// This is even faster than relying on the timer.// ---------------------------------------------------------------------#ifdef COMPILE_AS_EA
// .........................................................// EA version. Simply calls MainLoop() from a timer.// The timer has previously been created in OnInit(), // though we also check this in OnTick()...voidOnTimer ()
{
MainLoop();
}
// The function which creates the timer if it hasn't yet been set upvoid CreateTimer()
{
if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer (SLEEP_MILLISECONDS);
}
// Timer creation can sometimes fail in OnInit(), at least// up until MT4 build 970, if MT4 is starting up with the EA already// attached to a chart. Therefore, as a fallback, we also // handle OnTick(), and thus eventually set up the timer in// the EA's first tick if we failed to set it up in OnInit().// Similarly, the call to WSAAsyncSelect() may not be possible// in OnInit(), because it requires a chart handle, and that does// not exist in OnInit() during MT4 start-up.voidOnTick ()
{
CreateTimer();
SetupAsyncSelect();
}
// In the context of an EA, we can improve the latency even further by making the // processing event-driven, rather than just on a timer. If we use// WSAAsyncSelect() to tell Windows to send a WM_KEYDOWN whenever there's socket// activity, then this will fire the EA's OnChartEvent below. We can thus collect// socket events even faster than via the timed check, which becomes a back-up.// Note that WSAAsyncSelect() on a server socket also automatically applies // to all client sockets created from it by accept(), and also that// WSAAsyncSelect() puts the socket into non-blocking mode, duplicating what// we have already above using ioctlsocket().// The further complication is that WSAAsyncSelect() requires a window handle,// and this is not available in OnInit() during MT4 start-up. Therefore,// like the timer though for different reasons, we repeat the call to// this function during OnTick()void SetupAsyncSelect()
{
if (DoneAsyncSelect) return ;
if (WSAAsyncSelect(ServerSocket, ( int ) ChartGetInteger ( 0 , CHART_WINDOW_HANDLE ), 0x100/* WM_KEYDOWN */ , 0xFF/* All events */ ) == 0 ) {
DoneAsyncSelect = true ;
}
}
// In an EA, the use of WSAAsyncSelect() above means that Windows will fire a key-down // message whenever there is socket activity. We can then respond to KEYDOWN events// in MQL4 as a way of knowing that there is socket activity to be dealt with.voidOnChartEvent ( constint id, constlong & lparam, constdouble & dparam, conststring & sparam)
{
if (id == CHARTEVENT_KEYDOWN ) {
// For a pseudo-keypress from WSAAsyncSelect(), the lparam will be the socket handle// (and the dparam will be the type of socket event, e.g. dparam = 1 = FD_READ).// Of course, this event can also be fired for real keypresses on the chart...// What is the lparam?if (lparam == ServerSocket) {
// New pending connection on the listening server socket
AcceptNewConnections();
} else {
// See if lparam is one of the client sockets, by looping // through the Clients[] arrayint ctarr = ArraySize (Clients);
for ( int i = ctarr - 1 ; i >= 0 ; i--) {
if (Clients[i].SocketHandle() == lparam) {
// Yes, we have found a socket matching this "keyboard" event.// Return value from ReadAnyPendingData() is true// if the socket still seems to be alive; false if // the connection seems to have been closed, and should be discardedif (Clients[i].ReadAnyPendingData()) {
// Socket still seems to be alive
} else {
// Socket appears to be dead
ReleaseClientSocket(i, ctarr);
}
return ; //Early exit!
}
}
// If we get here, then the lparam does not match any// of the sockets, and the event must be a real keypress,// not a pseudo-keypress from WSAAsyncSelect()
}
}
}
#else
// .........................................................// Script version. Simply calls MainLoop() repeatedly// until the script is removed, with a small sleep between callsvoidOnStart ()
{
while (! IsStopped ()) {
MainLoop();
Sleep (SLEEP_MILLISECONDS);
}
}
#endif
// ---------------------------------------------------------------------// Main processing loop which handles new incoming connections, and reads// data from existing connections.// Can either be called from OnTimer() in an EA; or from OnStart() in a script, // on a continuous loop until IsStopped() is true. In an EA, socket // activity should almost always get handled by the event-driven stuff // above, and this timer loop is just a fallback.// ---------------------------------------------------------------------void MainLoop()
{
// Do nothing if init was unsuccessfulif (!SuccessfulInit) return ;
// This main timer loop does two things: accepts new incoming // connections, and reads pending data on all sockets (including// new ones which have just been accepted)
AcceptNewConnections();
ReadSocketMessages();
}
// Accept any new pending connections on the main listening server socket,// wrapping the new caller in a Connection object and putting it// into the Clients[] arrayvoid AcceptNewConnections()
{
int selres = socketselect3264(ServerSocket);
if (selres > 0 ) {
Print ( "New incoming connection..." );
int NewClientSocket = accept(ServerSocket, 0 , 0 );
if (NewClientSocket == - 1 ) {
if (WSAGetLastError() == 10035/* WSAEWOULDBLOCK */ ) {
// Blocking warning; ignorePrint ( "... would block; ignore" );
} else {
Print ( "ERROR " , WSAGetLastError() , " in socket accept" );
}
} else {
Print ( "...accepted" );
// Create a new Connection object to wrap the client socket,// and add it to the Clients[] arrayint ctarr = ArraySize (Clients);
ArrayResize (Clients, ctarr + 1 );
Clients[ctarr] = new Connection(NewClientSocket);
Print ( "Got connection to client #" , Clients[ctarr].GetID());
}
}
}
// Process any incoming data from all client connections// (including any which have just been accepted, above)void ReadSocketMessages()
{
int ctarr = ArraySize (Clients);
for ( int i = ctarr - 1 ; i >= 0 ; i--) {
// Return value from ReadAnyPendingData() is true// if the socket still seems to be alive; false if // the connection seems to have been closed, and should be discardedif (Clients[i].ReadAnyPendingData()) {
// Socket still seems to be alive
} else {
// Socket appears to be dead. Delete, and remove from list
ReleaseClientSocket(i, ctarr);
}
}
}
// Discards a client socket which appears to have died, deleting the Connection class// and removing it from the Clients[] array. Takes two parameters: the index of // the connection within the Clients[] array, and the current size of that array (which// is passed by reference, with the function sending back the decremented size)void ReleaseClientSocket( int idxReleaseAt, int & SizeOfArray)
{
Print ( "Lost connection to client #" , Clients[idxReleaseAt].GetID());
delete Clients[idxReleaseAt];
for ( int j = idxReleaseAt + 1 ; j < SizeOfArray; j++) {
Clients[j - 1 ] = Clients[j];
}
SizeOfArray--;
ArrayResize (Clients, SizeOfArray);
}
// Wraps the Winsock select() function, doing an immediate non-blocking// check of a single socket. This is the one area where we must// vary things between 32-bit and 64-bit. On 64-bit MT5, we can get away// with using Winsock calls where everything is defined as a 32-bit int;// we can rely on the fact that socket and window handles are only ever going// to be 4 bytes rather than 8 bytes - naughty, but works in practice.// The one exception is the fd_set structure. On 64-bit MT5, // we must pass a version of the fd_set where everything is defined// as 8-byte long rather than 4-byte int. Note that this applies to// the MT5 version, not the O/S. On 32-bit MT5, even on a 64-bit computer,// we use the same structure as MT4: 4-byte int rather than 8-byte longint socketselect3264( int TestSocket)
{
timeval waitfor;
waitfor.secs = 0 ;
waitfor.usecs = 0 ;
// The following, incidentally, is permitted on MT4, but obviously returns false...if ( TerminalInfoInteger ( TERMINAL_X64 )) {
fd_set64 PollSocket64;
PollSocket64.count = 1 ;
PollSocket64.single_socket = TestSocket;
return select( 0 , PollSocket64, 0 , 0 , waitfor);
} else {
fd_set PollSocket32;
PollSocket32.count = 1 ;
PollSocket32.single_socket = TestSocket;
return select( 0 , PollSocket32, 0 , 0 , waitfor);
}
}
// ---------------------------------------------------------------------// Termination - clean up sockets// ---------------------------------------------------------------------voidOnDeinit ( constint reason)
{
closesocket(ServerSocket);
for ( int i = 0 ; i < ArraySize (Clients); i++) {
delete Clients[i];
}
ArrayResize (Clients, 0 );
#ifdef COMPILE_AS_EA
EventKillTimer ();
CreatedTimer = false ;
DoneAsyncSelect = false ;
#endif
}
// ---------------------------------------------------------------------// Simple wrapper around each connected client socket// ---------------------------------------------------------------------class Connection {
private :
// Client socket handleint mSocket;
// Temporary buffer used to handle incoming datauchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
// Stored-up data, waiting for a \r character string mPendingData;
public :
Connection( int ClientSocket);
~Connection();
string GetID() { returnIntegerToString (mSocket);}
int SocketHandle() { return mSocket;}
bool ReadAnyPendingData();
void ProcessIncomingMessage( string strMessage);
};
// Constructor, called with the handle of a newly accepted socket
Connection::Connection( int ClientSocket)
{
mPendingData = "" ;
mSocket = ClientSocket;
// Put the client socket into non-blocking modeuint nbmode = 1 ;
if (ioctlsocket(mSocket, 0x8004667E/* FIONBIO */ , nbmode) != 0 ) { Print ( "ERROR in setting non-blocking mode on client socket" );}
}
// Destructor. Simply close the client socket
Connection::~Connection()
{
closesocket(mSocket);
}
// Called repeatedly on a timer, to check whether any// data is available on this client connection. Returns true if the // client still seems to be connected (*not* if there's new data); // returns false if the connection seems to be dead. bool Connection::ReadAnyPendingData()
{
// Check the client socket for data-readabilityint selres = socketselect3264(mSocket);
if (selres > 0 ) {
// Winsock says that there is data waiting to be read on this socketint szData = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0 );
if (szData > 0 ) {
// Convert the buffer to a string, and add it to any pending// data which we already have on this connectionstring strIncoming = CharArrayToString (mTempBuffer, 0 , szData);
mPendingData += strIncoming;
// Do we have a complete message (or more than one) ending in \r?int idxTerm = StringFind (mPendingData, "\r" );
while (idxTerm >= 0 ) {
if (idxTerm > 0 ) {
string strMsg = StringSubstr (mPendingData, 0 , idxTerm);
// Strip out any \n characters lingering from \r\n combosStringReplace (strMsg, "\n" , "" );
// Print the \r-terminated message in the log
ProcessIncomingMessage(strMsg);
}
// Keep looping until we have extracted all the \r delimited // messages, and leave any residue in the pending data
mPendingData = StringSubstr (mPendingData, idxTerm + 1 );
idxTerm = StringFind (mPendingData, "\r" );
}
returntrue ;
} elseif (WSAGetLastError() == 10035/* WSAEWOULDBLOCK */ ) {
// Would block; not an errorreturntrue ;
} else {
// recv() failed. Assume socket is deadreturnfalse ;
}
} elseif (selres == - 1 ) {
// Assume socket is deadreturnfalse ;
} else {
// No pending datareturntrue ;
}
}
// Can override this with whatever you want to do with incoming messages:// handling trading commands, send data back down the socket etc etc etcvoid Connection::ProcessIncomingMessage( string strMessage)
{
Print ( "#" , GetID() , ": " , strMessage);
}
Вместо того, чтобы размещать такие длинные SRC, подумайте о том, чтобы просто прикрепить сам файл. Я бы предложил вам отредактировать ваши длинные сообщения, удалив секции SRC и просто прикрепив исходный файл.
Вместо того, чтобы размещать такие длинные SRC, подумайте о том, чтобы просто прикрепить сам файл. Я бы предложил вам отредактировать ваши длинные сообщения, удалив секции SRC и просто прикрепив вместо них исходный файл.
Большое спасибо за ваш вклад.
Но, среди многих причин, почему я не собираюсь этого делать, является тот факт, что приведенный выше код работает как на MT4, так и на MT5. Если бы я загрузил его как вложение, он был бы введен в заблуждение и был бы помечен как .mq4 или .mq5, тогда как на самом деле он работает и там, и там.
Еще одной из ключевых причин является следующий сценарий, который мог бы быть применен к ОП:
" Я знаю довольно много о программировании сокетов на таком языке, как Java. Мне интересно, написал ли кто-нибудь сокет-сервер для MT4? Если да, то он должен использовать такие функции, как listen(), bind(), htons() и т.д.. Поэтому я попытаюсь найти такую статью, выполнив поиск в Google по запросу "mql4 bind listen htons".
Насколько я знаю, ни поисковые системы, ни поиск на этом сайте не могут просматривать вложения. Поэтому, если вы разместите код в виде вложения, он будет невидим для поиска по запросу "mql4 bind listen htons".
Вы регулярно говорите людям, что им следовало бы воспользоваться поиском или обратиться к документации; я пытаюсь помочь именно в этом.
(Так получилось, что "mql4 bind listen htons" не находит эту тему, потому что Google не просматривал страницу с 14 сентября. Но скоро она должна появиться в Google).
Но, среди многих причин, по которым я не собираюсь этого делать, является тот факт, что приведенный выше код работает как на MT4, так и на MT5. Если бы я загрузил его как вложение, он был бы введен в заблуждение и был бы помечен как .mq4 или .mq5, тогда как на самом деле он работает и там, и там.
В этом случае сделайте его включаемым заголовочным файлом (".mqh"). Таким образом, он будет многократно использоваться как в MQL4, так и в MQL5. Только убедитесь, что не вставляете код непосредственно в обработчики событий, OnInit(), OnDeinit() и т.д. - Дайте этим функциям отдельные и уникальные имена, и тогда пользователь просто должен будет поместить вызов к ним в свой собственный код из этих обработчиков.
Работает на MT5 (как 32-бит, так и 64-бит), а также на MT4
Устраняет тот факт, что CHART_WINDOW_HANDLE может не сработать в OnInit
Делает более быструю обработку событийных уведомлений через OnChartEvent, проверяя только конкретный сокет, который сгенерировал событие, вместо того, чтобы запускать весь MainLoop()
Опционально разрешает подключения с любого компьютера, а не только с localhost, переключение осуществляется с помощью параметра в Inputs
Еще раз спасибо jjc,
Сейчас я тороплюсь и у меня нет времени на тестирование, но я сделаю это на следующей неделе и расскажу вам. Я думаю, что у меня не будет проблем с запуском, но, возможно, я задам вам несколько вопросов. BTW: Пожалуйста, не редактируйте сообщения, они все идеальны как они есть (я чувствую себя как в StackOverflow =)).
Для авторизации и пользования сайтом MQL5.com необходимо разрешить использование файлов Сookie.
Пожалуйста, включите в вашем браузере данную настройку, иначе вы не сможете авторизоваться.
Вы вошли в аккаунт. Двухфакторная аутентификация отключена. Настройте её заново и отсканируйте новый QR-код или продолжите с отключённой двухфакторной аутентификацией.
... констатируя очевидное, если вы хотите сделать вышеописанное в советнике, а не в скрипте [...].
Переработанная версия, которую можно использовать для создания советника, а не скрипта, просто убрав комментарий #define COMPILE_AS_EA. Тогда код будет иметь OnTimer (и OnTick) вместо OnStart.
Единственным другим изменением является то, что код теперь переводит сокеты в неблокирующий режим. Он по-прежнему опрашивает наличие новых событий сокета с помощью select(), но теперь он избегает опасности вхождения в состояние блокировки, если select() окажется ошибочным.
Пересмотренная версия, которая [...].
Я уверен, что все с нетерпением ждут обновления по этому вопросу...
В контексте эксперта - который теперь используется по умолчанию в коде - оказывается, что можно использовать WSAAsyncSelect() для событийно-управляемой обработки активности сокета. Это часто позволяет снизить задержку между отправкой и получением сообщения ниже уровня миллисекунд. Точная скорость зависит от того, что еще делает MT4, но в среднем это намного быстрее, и никогда не хуже, чем просто использование таймера.
Невозможно заставить WSAAsyncSelect() вызвать OnTimer() или OnTick() в советнике. Например, можно сказать WSAAsyncSelect(), чтобы она отправляла сообщения WM_TIMER, но MT4 игнорирует их, потому что идентификатор таймера wparam из WSAAsyncSelect() не совпадает с идентификатором таймера, который она ожидает от EventSetMillisecondTimer().
Однако, указание WSAAsyncSelect() посылать WM_KEYDOWN успешно запускает OnChartEvent() в советнике. См. комментарии в коде для получения подробной информации о том, как работает это дополнение.
Для развлечения...
Лол. Потрясающе. Поздравляю. Я и не думал, что на MQL можно написать такие функции.
Лол. Потрясающе. Поздравляю. Я и не думал, что на MQL можно реализовать такой функционал.
Мой инстинкт - всегда делать серверные сокеты с использованием многопоточности, но я не могу придумать причину, по которой этот код не был бы жизнеспособным и безопасным для реального использования.
В последней версии, приведенной выше, есть одна вещь, которая не является идеальной. Вместо того чтобы публиковать еще один большой блок кода, я опишу его:
Это небольшое изменение в коде, но я не хочу постить еще один большой блок в этот форум, когда это интересно только вам.
Вместо того, чтобы публиковать еще один большой блок кода [...]
На второй размышления...
Еще одна версия со следующими изменениями:
Вместо того, чтобы размещать такие длинные SRC, подумайте о том, чтобы просто прикрепить сам файл. Я бы предложил вам отредактировать ваши длинные сообщения, удалив секции SRC и просто прикрепив исходный файл.
Вместо того, чтобы размещать такие длинные SRC, подумайте о том, чтобы просто прикрепить сам файл. Я бы предложил вам отредактировать ваши длинные сообщения, удалив секции SRC и просто прикрепив вместо них исходный файл.
Большое спасибо за ваш вклад.
Но, среди многих причин, почему я не собираюсь этого делать, является тот факт, что приведенный выше код работает как на MT4, так и на MT5. Если бы я загрузил его как вложение, он был бы введен в заблуждение и был бы помечен как .mq4 или .mq5, тогда как на самом деле он работает и там, и там.
Но среди множества причин [...].
Еще одной из ключевых причин является следующий сценарий, который мог бы быть применен к ОП:
Насколько я знаю, ни поисковые системы, ни поиск на этом сайте не могут просматривать вложения. Поэтому, если вы разместите код в виде вложения, он будет невидим для поиска по запросу "mql4 bind listen htons".
Вы регулярно говорите людям, что им следовало бы воспользоваться поиском или обратиться к документации; я пытаюсь помочь именно в этом.
(Так получилось, что "mql4 bind listen htons" не находит эту тему, потому что Google не просматривал страницу с 14 сентября. Но скоро она должна появиться в Google).
Большое спасибо за ваш вклад.
Но, среди многих причин, по которым я не собираюсь этого делать, является тот факт, что приведенный выше код работает как на MT4, так и на MT5. Если бы я загрузил его как вложение, он был бы введен в заблуждение и был бы помечен как .mq4 или .mq5, тогда как на самом деле он работает и там, и там.
Если подумать...
Еще одна версия, со следующими изменениями:
Еще раз спасибо jjc,
Сейчас я тороплюсь и у меня нет времени на тестирование, но я сделаю это на следующей неделе и расскажу вам. Я думаю, что у меня не будет проблем с запуском, но, возможно, я задам вам несколько вопросов. BTW: Пожалуйста, не редактируйте сообщения, они все идеальны как они есть (я чувствую себя как в StackOverflow =)).