단순히 #define COMPILE_AS_EA의 주석을 제거하여 스크립트가 아닌 EA를 생성하는 데 사용할 수 있는 개정된 버전입니다. 그런 다음 코드에는 OnStart 대신 OnTimer(및 OnTick)가 있습니다.
다른 유일한 변경 사항은 이제 코드가 소켓을 비차단 모드로 전환한다는 것입니다. 여전히 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);
}
이제 코드의 기본값이 된 EA의 컨텍스트에서 WSAAsyncSelect()를 사용하여 소켓 활동의 이벤트 기반 처리를 수행하는 것이 가능합니다. 이것은 종종 메시지 전송과 메시지 수신 사이의 대기 시간을 밀리초 수준 이하로 높일 수 있습니다. 정확한 속도는 MT4가 수행하는 다른 작업에 따라 다르지만 평균적으로 타이머를 사용하는 것보다 훨씬 빠르며 결코 나쁘지 않습니다.
EA에서 WSAAsyncSelect()가 OnTimer() 또는 OnTick()을 트리거하도록 할 수 없습니다. 예를 들어 WSAAsyncSelect()는 WM_TIMER 메시지를 실행하도록 지시할 수 있지만 WSAAsyncSelect()의 wparam 타이머 ID가 EventSetMillisecondTimer()의 예상 타이머 ID와 일치하지 않기 때문에 MT4는 이를 무시합니다.
그러나 WSAAsyncSelect()에 WM_KEYDOWN을 보내도록 지시하면 EA에서 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);
}
ONInit에서 CHART_WINDOW_HANDLE이 실패할 수 있다는 사실을 처리합니다.
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);
}
" Java와 같은 언어로 소켓 프로그래밍에 대해 꽤 알고 있습니다. MT4용 소켓 서버를 작성한 사람이 있는지 궁금합니다. 그렇다면, listen( ) , bind(), htons( ) 등등. 그래서 구글에서 'mql4 bind listen htons'로 검색해서 그런 기사를 찾아보겠다."
내가 아는 한, 검색 엔진이나 이 사이트의 검색은 첨부 파일 내부를 볼 수 없습니다. 따라서 코드를 첨부 파일로 게시하면 "mql4 bind listen htons"와 같은 검색에서 코드가 보이지 않게 됩니다.
사람들에게 정기적으로 문서를 검색하거나 참조했어야 한다고 말합니다. 나는 바로 그것을 도우려고 노력하고 있다.
(그런데 "mql4 bind listen htons"는 Google이 9월 14일 이후로 페이지를 크롤링하지 않았기 때문에 이 주제를 찾지 못합니다. 그러나 곧 Google에 표시되기 시작할 것입니다.)
그러나 내가 이것을 하지 않을 많은 이유 중 하나는 위의 코드가 MT4와 MT5 모두에서 작동한다는 사실입니다. 첨부 파일로 업로드하면 .mq4 또는 .mq5로 잘못 표시되어야 하지만 실제로는 둘 다로 작동합니다.
이 경우 포함 헤더 파일(".mqh")로 만듭니다. 그렇게 하면 MQL4와 MQL5 모두에서 재사용할 수 있습니다. 이벤트 핸들러, OnInit() , OnDeinit() 등에 코드를 직접 포함하지 않도록 하십시오. - 해당 함수 에 별도의 고유한 이름을 부여한 다음 사용자는 자체 코드에서 해당 함수를 호출하기만 하면 됩니다. 그 핸들러.
ONInit에서 CHART_WINDOW_HANDLE이 실패할 수 있다는 사실을 처리합니다.
OnChartEvent를 통해 이벤트 기반 알림을 더 빠르게 처리하고 전체 MainLoop()을 실행하는 대신 이벤트를 생성한 특정 소켓만 확인합니다.
선택적으로 로컬 호스트가 아닌 모든 컴퓨터에서 연결을 허용하고 입력의 매개변수를 통해 전환 가능
다시 한번 jjc님, 감사합니다.
지금은 바빠서 테스트 할 시간이 없지만 다음주에 해보고 알려드리겠습니다. 나는 그것을 실행하는 데 문제가 없을 것이라고 생각하지만 아마도 당신에게 몇 가지 질문을 할 것입니다. BTW: 게시물을 편집하지 마십시오. 그것들은 모두 있는 그대로 완벽합니다(저는 StackOverflow =에서 같은 느낌입니다).
... 스크립트가 아닌 EA에서 위의 작업을 수행하려는 경우 [...]
단순히 #define COMPILE_AS_EA의 주석을 제거하여 스크립트가 아닌 EA를 생성하는 데 사용할 수 있는 개정된 버전입니다. 그런 다음 코드에는 OnStart 대신 OnTimer(및 OnTick)가 있습니다.
다른 유일한 변경 사항은 이제 코드가 소켓을 비차단 모드로 전환한다는 것입니다. 여전히 select()를 사용하여 새 소켓 이벤트의 가용성을 폴링하지만 이제 select()가 어떻게든 잘못된 경우 차단 상태로 들어가는 위험을 방지합니다.
개정된 버전 [...]
나는 모두가 이것에 대한 업데이트를 열망하고 있다고 확신합니다 ...
이제 코드의 기본값이 된 EA의 컨텍스트에서 WSAAsyncSelect()를 사용하여 소켓 활동의 이벤트 기반 처리를 수행하는 것이 가능합니다. 이것은 종종 메시지 전송과 메시지 수신 사이의 대기 시간을 밀리초 수준 이하로 높일 수 있습니다. 정확한 속도는 MT4가 수행하는 다른 작업에 따라 다르지만 평균적으로 타이머를 사용하는 것보다 훨씬 빠르며 결코 나쁘지 않습니다.
EA에서 WSAAsyncSelect()가 OnTimer() 또는 OnTick()을 트리거하도록 할 수 없습니다. 예를 들어 WSAAsyncSelect()는 WM_TIMER 메시지를 실행하도록 지시할 수 있지만 WSAAsyncSelect()의 wparam 타이머 ID가 EventSetMillisecondTimer()의 예상 타이머 ID와 일치하지 않기 때문에 MT4는 이를 무시합니다.
그러나 WSAAsyncSelect()에 WM_KEYDOWN을 보내도록 지시하면 EA에서 OnChartEvent()가 성공적으로 실행됩니다. 이 추가가 작동하는 방식에 대한 자세한 내용은 코드의 주석을 참조하십시오.
약간의 재미를 위해...
ㅋㅋㅋ. 놀라운. 축하해요. MQL에서 이러한 기능 을 코딩할 수 있을 거라고는 생각하지 못했습니다.
ㅋㅋㅋ. 놀라운. 축하해요. MQL에서 이러한 기능을 코딩할 수 있을 거라고는 생각하지 못했습니다.
내 본능은 항상 멀티 스레딩을 사용하여 서버 소켓을 수행하는 것이지만 실제로 이 코드가 실제 사용에 적합하지 않고 안전하지 않은 이유를 생각할 수 없습니다.
위의 최신 버전에는 이상적이지 않은 한 가지가 있습니다. 또 다른 큰 코드 블록을 게시하는 대신 다음과 같이 설명하겠습니다.
코드에 대한 작은 변경이지만 관심이 있을 때만 이 포럼에 또 다른 큰 블록을 게시하고 싶지 않습니다.
또 다른 큰 코드 블록을 게시하는 대신 [...]
다시 생각에...
다음 변경 사항이 포함된 또 다른 버전:
이렇게 긴 SRC 기고문을 게시하는 대신 파일 자체를 첨부하는 것을 고려하십시오. 긴 게시물을 편집하고 SRC 섹션을 삭제하고 소스 파일만 첨부하는 것이 좋습니다.
이렇게 긴 SRC 기고문을 게시하는 대신 파일 자체를 첨부하는 것을 고려하십시오. 더 긴 게시물을 편집하고 SRC 섹션을 삭제하고 대신 소스 파일을 첨부하는 것이 좋습니다.
귀하의 기여에 진심으로 감사드립니다.
그러나 내가 이것을 하지 않을 많은 이유 중 하나는 위의 코드가 MT4와 MT5 모두에서 작동한다는 사실입니다. 첨부 파일로 업로드하면 오해의 소지가 있는 .mq4 또는 .mq5 플래그가 지정되어야 하지만 실제로는 둘 다로 작동합니다.
그러나 많은 이유 중에서 [...]
또 다른 주요 이유는 OP에 적용될 수 있는 다음 시나리오입니다.
내가 아는 한, 검색 엔진이나 이 사이트의 검색은 첨부 파일 내부를 볼 수 없습니다. 따라서 코드를 첨부 파일로 게시하면 "mql4 bind listen htons"와 같은 검색에서 코드가 보이지 않게 됩니다.
사람들에게 정기적으로 문서를 검색하거나 참조했어야 한다고 말합니다. 나는 바로 그것을 도우려고 노력하고 있다.
(그런데 "mql4 bind listen htons"는 Google이 9월 14일 이후로 페이지를 크롤링하지 않았기 때문에 이 주제를 찾지 못합니다. 그러나 곧 Google에 표시되기 시작할 것입니다.)
귀하의 기여에 진심으로 감사드립니다.
그러나 내가 이것을 하지 않을 많은 이유 중 하나는 위의 코드가 MT4와 MT5 모두에서 작동한다는 사실입니다. 첨부 파일로 업로드하면 .mq4 또는 .mq5로 잘못 표시되어야 하지만 실제로는 둘 다로 작동합니다.
다시 생각에...
다음 변경 사항이 포함된 또 다른 버전:
다시 한번 jjc님, 감사합니다.
지금은 바빠서 테스트 할 시간이 없지만 다음주에 해보고 알려드리겠습니다. 나는 그것을 실행하는 데 문제가 없을 것이라고 생각하지만 아마도 당신에게 몇 가지 질문을 할 것입니다. BTW: 게시물을 편집하지 마십시오. 그것들은 모두 있는 그대로 완벽합니다(저는 StackOverflow =에서 같은 느낌입니다).