#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);
}
#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中而不是在脚本中进行上述操作[......] 。
修订后的版本可以用来创建一个EA而不是脚本,只需取消#define COMPILE_AS_EA的注释即可。这样,代码就有了OnTimer(和OnTick)而不是OnStart。
其他唯一的变化是,代码现在将套接字放入非阻塞模式。它仍然使用select()来轮询新的套接字事件的可用性,但它现在避免了在select()出错时进入阻塞状态的危险。
修订版,其中[...] 。
我相信大家都很想知道这方面的更新...
在EA的背景下--现在代码中是默认的--事实证明可以使用WSAAsyncSelect()来做事件驱动的套接字活动处理。这通常可以将消息发送和消息接收之间的延迟推到毫秒级别以下。确切的速度取决于MT4正在做什么,但平均而言,它比使用定时器快得多,而且从不差。
不可能让WSAAsyncSelect()在EA中触发OnTimer()或OnTick()。例如,可以告诉WSAAsyncSelect()发射WM_TIMER信息,但MT4忽略了这些信息,因为来自WSAAsyncSelect()的wparam计时器ID与它从EventSetMillisecondTimer()期望的计时器ID不匹配。
然而,告诉WSAAsyncSelect()发送WM_KEYDOWN确实成功地触发了EA中的OnChartEvent()。请看代码中的注释,以了解这一补充工作的全部细节。
为了一点乐趣...
傻子。真了不起。祝贺你。我不认为有可能在MQL上编写这些功能 的代码。
大笑。令人惊讶。祝贺你。我没有想到可以在MQL上编写这些功能的代码。
我的本能总是使用多线程做服务器套接字,但我实际上想不出为什么这段代码在现实生活中不可行和不安全。
在上面的最新版本中,有一件事并不理想。与其再贴出一大块代码,我不如把它描述出来。
这是对代码的一个小改动,但我不想在只有你感兴趣的时候把另一个大块头贴到这个论坛上。
而不是发布另一个大块代码 [...]
回头一想...
另一个版本,具有以下更改:
与其发表这么长的SRC贡献,不如考虑直接附上文件本身。我建议你编辑你的长文章,删除SRC部分,只附上源文件。
与其发表这么长的SRC贡献,不如考虑直接附上文件本身。我建议你编辑你的长文章,删除SRC部分,只附上源文件。
非常感谢你的贡献。
但是,在我不打算这样做的众多原因中,有一个事实是上面的代码在MT4和MT5上都适用。如果我把它作为附件上传,它会误导性地被标记为.mq4或.mq5,而事实上它两者都能使用。
但是,在众多的原因中[...] 。
另一个关键原因是以下情况,这可能适用于OP的情况。
据我所知,搜索引擎和本网站的搜索都不能查看附件内部。因此,将代码作为附件发布会使它在 "mql4 bind listen htons "这样的搜索中不可见。
你经常告诉人们,他们应该进行搜索或查阅文档;我正是想在这方面提供帮助。
(碰巧的是,"mql4 bind listen htons "并没有找到这个主题,因为谷歌从9月14日开始就没有抓取过这个页面)。但它应该很快就会开始出现在谷歌上)。
非常感谢您的贡献。
但是,我不打算这样做的原因之一是,上述代码在MT4和MT5上都适用。如果我把它作为附件上传,它会误导性地被标记为.mq4或.mq5,而事实上它两者都能使用。
仔细想想...
然而,另一个版本,有以下变化。
再次感谢jjc。
现在我很匆忙,没有时间测试,但我下周会测试并告诉你。我想我不会有任何问题,但也许我会问你一些问题。BTW:请不要编辑任何帖子;它们都是完美的,因为它们是(我觉得在StackOverflow=))。