#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が他に何を行っているかによって異なりますが、平均して、タイマーを使用するよりもはるかに速く、決して悪くなることはありません。
#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);
}
// *********************************************************************// 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);
}
...当たり前のことですが、もし上記をスクリプトではなくEAで行いたい場合 [...]...
define COMPILE_AS_EA のコメントを外すだけで、スクリプトではなくEAを作成することができる改訂版です。このコードでは、OnStartの代わりにOnTimer(とOnTick)を使っています。
他の唯一の変更は、コードがソケットをノンブロッキングモードにしたことです。select()を使って新しいソケットイベントがあるかどうかポーリングしていますが、select()が何らかの理由で間違っていた場合にブロッキング状態になる危険性を回避できるようになりました。
した改訂版[...]
皆さん、これのアップデートを熱望していると思います...。
EA のコンテキストでは、WSAAsyncSelect() を使ってソケットのアクティビティをイベントドリブンで処理できることがわかりました。これにより、メッセージ送信とメッセージ受信の間の待ち時間をミリ秒レベル以下にできることがよくあります。正確な速度は、MT4が他に何を行っているかによって異なりますが、平均して、タイマーを使用するよりもはるかに速く、決して悪くなることはありません。
WSAAsyncSelect()がEA内の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に改めて感謝します。
今は急いでいてテストする時間がないのですが、来週にはテストしてお伝えします。でも、もしかしたら、疑問があるかもしれません。ちなみに、どの投稿も編集しないでください。