Simulación de mercado (Parte 09): Sockets (III)
Introducción
En el artículo anterior, «Simulación de mercado (Parte 08): Sockets (II)», comenzamos a desarrollar una aplicación práctica que hace uso de los sockets. El objetivo es demostrar cómo se utiliza esta herramienta en la programación orientada a MetaTrader 5. Es cierto que MQL5 no permite crear un servidor directamente con MQL5 puro. Sin embargo, dado que el uso de sockets no depende de ningún lenguaje ni siquiera del sistema operativo, aún podemos utilizarlos en MetaTrader 5 programando a través de MQL5.
Sin embargo, por motivos internos de la propia plataforma MetaTrader 5, no podemos utilizar indicadores junto con sockets. En otras palabras, no podemos incluir en el código de un indicador llamadas a procedimientos que involucren sockets. Si fuera posible, podríamos bloquear o comprometer el rendimiento de los cálculos realizados dentro de los indicadores.
No obstante, nada nos impide utilizar los indicadores para otros fines. Precisamente eso es lo que hicimos en el artículo anterior, donde creamos todo nuestro minichat, incluidos los controles y el panel de texto, dentro de un indicador. Los elementos creados e insertados en el indicador no interfieren en absoluto con el flujo de ejecución de los indicadores. Sin embargo, sin el uso de un indicador, habría sido bastante complicado crear lo que se hizo en el artículo anterior, ya que habríamos terminado interfiriendo en alguna región del gráfico del símbolo que se estuviera trazando.
De esa manera, no tenemos tales problemas, dejando así nuestro minichat aislado en su propia ventana.
Sin embargo, el ejecutable que se generará en el artículo anterior no es capaz de satisfacer nuestras necesidades para que el minichat funcione realmente. Necesitamos implementar algunos detalles más. Aquí, entonces, terminaremos de crear el soporte necesario para que tengamos el minichat funcionando en MetaTrader 5. Aun así, como se mencionó al principio, MQL5 no es capaz actualmente de cubrir todo lo que necesitamos, por lo que será necesario recurrir a programación externa. Pero, en este punto, hay algo que puede hacerse. No obstante, explicaré esto más adelante en este mismo artículo. Así que pongamos manos a la obra y finalicemos la parte que será desarrollada en MQL5.
Implementación de la clase principal
Antes de revisar el código fuente final del Asesor Experto, que permitirá que el minichat funcione, veamos el archivo de encabezado que implementará la clase de conexión. Puede verse completo a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class C_Connection 05. { 06. private : 07. int m_Socket; 08. char m_buff[]; 09. string m_info; 10. bool m_NewLine; 11. public : 12. //+------------------------------------------------------------------+ 13. C_Connection(string addr, ushort port, ushort timeout = 1000) 14. :m_Socket(INVALID_HANDLE), 15. m_info(""), 16. m_NewLine(true) 17. { 18. if ((m_Socket = SocketCreate()) == INVALID_HANDLE) Print("Unable to create socket. Error: ", GetLastError()); 19. else if (SocketConnect(m_Socket, addr, port, timeout)) return; 20. else Print("Connection with the address [", addr,"] in port: ",port, " failed. Error code: ", GetLastError()); 21. SetUserError(1); 22. } 23. //+------------------------------------------------------------------+ 24. ~C_Connection() 25. { 26. SocketClose(m_Socket); 27. } 28. //+------------------------------------------------------------------+ 29. bool ConnectionWrite(string szMsg) 30. { 31. int len = StringToCharArray(szMsg, m_buff) - 1; 32. 33. if (m_Socket != INVALID_HANDLE) 34. if (len >= 0) 35. if (SocketSend(m_Socket, m_buff, len) == len) 36. return true; 37. Print("Connection Write: FAILED..."); 38. 39. return false; 40. } 41. //+------------------------------------------------------------------+ 42. const string ConnectionRead(ushort timeout = 500) 43. { 44. int ret; 45. 46. if (m_NewLine) 47. { 48. m_info = ""; 49. m_NewLine = false; 50. } 51. ret = SocketRead(m_Socket, m_buff, SocketIsReadable(m_Socket), timeout); 52. if (ret > 0) 53. { 54. m_info += CharArrayToString(m_buff, 0, ret); 55. for (ret--; (ret >= 0) && (!m_NewLine); ret--) 56. m_NewLine = (m_buff[ret] == '\n') || (m_buff[ret] == '\r'); 57. } 58. 59. return (m_NewLine ? m_info : ""); 60. } 61. //+------------------------------------------------------------------+ 62. }; 63. //+------------------------------------------------------------------+
Archivo de cabecera C_Study.mqh
Quiero que prestes atención a ciertos detalles de este código, ya que entenderlos te permitirá comprender realmente cómo hacer que el minichat funcione. Nuestra clase contiene algunas variables globales privadas, como puedes ver entre las líneas siete y diez. Observa que todas esas variables aparecen después de la cláusula private.
Por lo tanto, no podrán accederse a ellas fuera de la clase. Lo primero que hacemos al utilizar esta clase es invocar su constructor. Así, la primera instrucción que se ejecuta es la de la línea 13. Ahora, observa que en las líneas 14, 15 y 16 se realizan algunas acciones. Lo que se está haciendo allí es inicializar las variables globales de la clase. Un detalle importante: hago esto aquí y de esta forma por costumbre, pero principalmente para evitar olvidarme de hacerlo dentro del cuerpo del constructor. Créeme, este tipo de olvido es muy común. Pero, al hacerlo así, no me olvido de inicializar correctamente las variables principales.
Muy bien. En cuanto el constructor comienza su ejecución, en la línea 18 usamos una llamada a la biblioteca estándar para intentar crear un socket. Si falla, imprimiremos un mensaje en MetaTrader 5. En caso de éxito, en la línea 19 intentaremos conectarnos al socket indicado por los parámetros. A diferencia de lo que sucede en la línea anterior, aquí, si tenemos éxito, retornaremos inmediatamente. La razón de esto es que la conexión ya se ha establecido y, en caso de fallo, la línea 20 nos informará de lo que ocurrió y cuál fue la causa del error.
De cualquier manera, si no es posible crear un socket o conectarnos a él, la línea 21 nos permitirá informar al código principal (que, en este caso, será el Asesor Experto) de que el proceso de conexión ha fallado. Cuando analicemos el código del Asesor Experto, explicaré cómo podrás detectar esta situación. Pero, la línea 21 solo se ejecutará si hay un error durante el intento de conexión.
El destructor, que se encuentra en la línea 24, tiene como único objetivo cerrar el socket. Por esta razón, solo tiene una línea en su cuerpo. Se trata de la línea 26, que libera el socket que habíamos utilizado, si es que se ha creado.
Ahora, veamos las dos funciones principales de nuestra clase. La primera de ellas se encarga de enviar datos a través de la conexión. Se trata exactamente de la función de escritura, que se encuentra en la línea 29. Esta función, al igual que la siguiente, debe implementarse con cierta atención, ya que todo el código no cambiará por completo, sin importar lo que se desee enviar: imagen, sonido, vídeo o texto, como será el caso aquí. Lo que realmente cambia son solo estas dos funciones: la de escritura y la de lectura. En este caso, estamos haciendo todo de la manera más simple posible. Por lo tanto, no utilizaremos cifrado ni ningún otro tipo de seguridad en el envío de la información. El motivo es sencillo: solo estamos probando y aprendiendo cómo funcionan los sockets.
Entonces, como solo transmitiremos texto, lo primero que debemos saber es cuántos caracteres se van a enviar. Esto se realiza en la línea 31. El hecho de restar uno al número de caracteres se debe a que la cadena recibida sigue el estándar C/C++. Es decir, termina con un carácter nulo. Ese carácter no lo transmitiremos nosotros. Como es posible que el constructor haya fallado durante la creación o incluso en la conexión, debemos verificar si el socket es válido antes de intentar transmitir algo a través de él. Esta comprobación se realiza en la línea 33. Si el socket es inválido por cualquier motivo, la transmisión no se llevará a cabo.
Una vez hecho esto, debemos comprobar otra cosa. En esta ocasión, debemos comprobar el número de caracteres que se van a transferir, ya que no tiene sentido intentar enviar algo si no hay nada que enviar. Esta verificación se lleva a cabo en la línea 34. Si todas estas pruebas se superan correctamente, en la línea 35 intentaremos enviar los datos deseados a través del socket abierto. En este caso, estamos enviando a través de una conexión normal, es decir, no estamos utilizando ningún proceso de cifrado ni de autenticación. Pero, MQL5 nos permite realizar envíos seguros. Consulta la documentación para entender mejor este tema.
Si los datos se envían con éxito, en la línea 36 se devuelve un valor verdadero al autor de llamada. Si alguna de las pruebas falla, en la línea 37 imprimiremos un mensaje en MetaTrader 5 y, en la línea 39, retornaremos un valor falso al autor de llamada.
Ahora veamos la función de lectura, que se encuentra en la línea 42. Aquí conviene hacer una breve pausa para explicar algo. Cada función de escritura debe tener una función de lectura correspondiente. No es obligatorio, pero imagina por un momento el siguiente escenario: podrías crear diversas funciones para enviar diferentes tipos de datos. En cierto modo, es adecuado que los datos se reciban a través de funciones apropiadas.
O, mejor dicho, ¿por qué intentarías leer un dato que representa un vídeo usando una función destinada a leer texto? No tendría sentido, ¿verdad? Por eso, asegúrate siempre de que los datos y el protocolo que utilizas son adecuados.
Esta parte del protocolo no está relacionada con el tipo de conexión TCP o UDP, sino con la forma en que se estructuró la información antes de transmitirse. Piensa siempre que los datos del socket se asemejan a un archivo que estás leyendo. Para interpretar correctamente su contenido, es necesario utilizar el protocolo de lectura adecuado.
No sirve de nada intentar leer un bitmap con un protocolo destinado a leer una imagen JPEG. Aunque ambos sean imágenes, la forma en que están estructurados dentro del archivo es completamente diferente. Lo mismo se aplica aquí cuando hablamos de sockets. Dicho esto, pasemos entonces a la rutina de lectura del socket.
A diferencia de lo visto en el artículo «Simulación de mercado (Parte 07): Sockets (I)», donde producíamos un eco en el servidor, aquí no quedamos necesariamente bloqueados esperando la respuesta del servidor. En realidad, esperamos un pequeño intervalo para que este nos responda. Pero la cuestión es que, independientemente de que responda más o menos rápido, retornaremos al autor de llamada incluso antes de que todo el mensaje haya sido leído del socket.
Pero, ¿cómo? ¿Podemos volver a nuestro código del Asesor Experto antes de leer completamente el socket? Sí, podemos, pero debemos tener ciertas precauciones al hacerlo. Por eso mencioné hace un momento que debías prestar atención al tratamiento adecuado del tipo de información esperada en el socket. En nuestra función de lectura, por defecto, esperamos 500 milisegundos antes de retornar. Transcurrido ese tiempo, regresaremos al código principal, aunque no haya información disponible en el socket.
Pero, si durante ese tiempo aparece alguna información, la leeremos en la línea 51. Ahora, presta mucha atención al siguiente detalle, ya que es importante si decides implementar tu propio servidor. En la línea 54, añadimos el contenido del socket a nuestra cadena de retorno. Sin embargo, esta cadena de retorno solo devolverá algo al autor de llamada si se encuentra un carácter de nueva línea o de retorno de carro en la cadena de caracteres. Para comprobarlo, utilizamos el bucle de la línea 55, que recorrerá el contenido recibido en busca de los caracteres mencionados.
Cuando se encuentra uno de estos caracteres, la variable m_NewLine recibe un valor verdadero. Así, en la línea 59, se permite que el contenido leído del socket se reenvíe al autor de llamada. Del mismo modo, cuando ocurre una nueva llamada, la comprobación de la línea 46 permite limpiar el contenido de la cadena y dar inicio a un nuevo ciclo.
Ahora, presta atención: en el código de la función de escritura en el socket, que se encuentra en la línea 29, no añadimos esos caracteres esperados por la función de lectura. Y el objeto OBJ_EDIT tampoco añade dichos caracteres, del mismo modo que los códigos que realizan las llamadas a la función ConnectionWrite tampoco lo hacen. Entonces, ¿quién está añadiendo esos caracteres? Quien lo hará será el servidor. Por lo tanto, si implementas tu propio servidor, deberás añadir esos caracteres; de lo contrario, el minichat no podrá distinguir entre un mensaje enviado y otro.
Puede parecer tonto o innecesario que estemos haciendo esto de esta manera, pero hacerlo así nos permite lograr algo que podrás ver en los vídeos incluidos en este artículo. Muy bien. Ahora que ya hemos visto el código de la clase de conexión, veamos el código del Asesor Experto.
Implementar el Asesor Experto
El código completo del Asesor Experto que implementa el minichat puede verse íntegramente a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. #property description "Mini Chat for demonstration of using sockets in MQL5." 04. #property link "https://www.mql5.com/pt/articles/12673" 05. #property version "1.00" 06. //+------------------------------------------------------------------+ 07. #define def_IndicatorMiniChat "Indicators\\Mini Chat\\Mini Chat.ex5" 08. #resource "\\" + def_IndicatorMiniChat 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\Mini Chat\C_Connection.mqh> 11. #include <Market Replay\Defines.mqh> 12. //+------------------------------------------------------------------+ 13. input string user00 = "127.0.0.1"; //Address 14. input ushort user01 = 27015; //Port 15. //+------------------------------------------------------------------+ 16. long gl_id; 17. int gl_sub; 18. C_Connection *Conn; 19. //+------------------------------------------------------------------+ 20. int OnInit() 21. { 22. Conn = new C_Connection(user00, user01); 23. if (_LastError > ERR_USER_ERROR_FIRST) 24. return INIT_FAILED; 25. ChartIndicatorAdd(gl_id = ChartID(), gl_sub = (int) ChartGetInteger(gl_id, CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_IndicatorMiniChat)); 26. 27. EventSetTimer(1); 28. 29. return INIT_SUCCEEDED; 30. } 31. //+------------------------------------------------------------------+ 32. void OnDeinit(const int reason) 33. { 34. EventKillTimer(); 35. delete Conn; 36. ChartIndicatorDelete(gl_id, gl_sub, ChartIndicatorName(gl_id, gl_sub, 0)); 37. } 38. //+------------------------------------------------------------------+ 39. void OnTick() 40. { 41. } 42. //+------------------------------------------------------------------+ 43. void OnTimer() 44. { 45. string sz0 = (*Conn).ConnectionRead(); 46. if (sz0 != "") 47. EventChartCustom(gl_id, evChatReadSocket, 0, 0, sz0); 48. } 49. //+------------------------------------------------------------------+ 50. void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) 51. { 52. if (id == CHARTEVENT_CUSTOM + evChatWriteSocket) 53. (*Conn).ConnectionWrite(sparam); 54. } 55. //+------------------------------------------------------------------+
Código fuente del EA
Como puedes ver, el código es bastante simple. Prácticamente no es necesario programar mucho. Aun así, hay algunos aspectos a los que debemos prestar atención. Fíjate en las líneas siete y ocho, donde añadimos al código del Asesor Experto el indicador creado en el artículo anterior. De esta forma, una vez compilado el código del Asesor Experto, se podrá eliminar el ejecutable del indicador, ya que estará incorporado en el Asesor Experto. En el artículo anterior expliqué los motivos de esta medida. El código se desarrolla sin demasiados cambios con respecto a lo visto en el artículo anterior.
Ahora, el detalle es que se ha añadido una llamada para recibir eventos del reloj. Esto se lleva a cabo en la línea 27. Así, aproximadamente cada segundo, se ejecutará el código encargado de gestionar los eventos del reloj, es decir, se producirá una llamada a OnTime. Lo que realmente nos interesa de este código del Asesor Experto es precisamente esa llamada a OnTime y la llamada a OnChartEvent. Estas dos son las que permiten que el minichat funcione como se está implementando. Entonces, primero comprendamos el procedimiento OnTime.
Cuando MetaTrader 5 dispare un evento OnTime, nuestro Asesor Experto interceptará la llamada. En la línea 45, intentaremos leer el socket indicado en las líneas 13 y 14, es decir, nos conectaremos a una red. Esto se explicó dos artículos atrás, cuando hablé sobre sockets. Si tienes dudas al respecto, revisa el artículo Sockets (I).
Bien. El intento de lectura puede devolver una cadena o no. Si la función de lectura devuelve una respuesta, la comprobación de la línea 46 será exitosa. Entonces, dispararemos un evento personalizado en la línea 47. El propósito de este evento es permitir precisamente que el indicador del minichat pueda acceder a la información publicada en el socket. Si el minichat estuviera implementado directamente dentro del Asesor Experto, dicho evento personalizado sería completamente innecesario. Pero, como hemos separado las cosas, es necesario enviar los datos al indicador para que se encargue de mostrar la información en el panel de texto. Fíjate en lo interesante de esto: dependiendo de lo que queramos mostrar, debemos adaptarnos a la mejor forma de implementar la solución.
En cuanto al procedimiento OnTime, no hay más detalles que añadir. Es así de simple. De la misma manera, el procedimiento OnChartEvent también es bastante sencillo. Solo que, en este caso, interceptaremos una solicitud del indicador del minichat para poder escribir en el socket. Para verificar si tenemos una llamada de este tipo, utilizamos la comprobación en la línea 52. Si se confirma que se trata de un evento personalizado, la línea 53 reenviará a la función de escritura los datos proporcionados por el indicador del minichat.
En este sistema de comunicación hay un detalle que puede no resultar del todo claro para quienes no están familiarizados con la programación orientada a eventos. El detalle es que, si cualquier otro programa (ya sea otro indicador, un script o incluso un servicio) dispara un evento personalizado con el mismo valor que evChatReadSocket, el indicador del minichat lo interpretará como si proviniera del Asesor Experto. Lo mismo ocurre si un evento personalizado disparado tiene el mismo valor que evChatWriteSocket. En ese caso, el Asesor Experto interpretará la situación como si el evento proviniera del indicador del minichat.
No sé si has notado las posibilidades que esto abre, estimado lector. Aún más si sabes cuál es la dirección y el puerto que deben recibir los datos que vas a colocar en el socket, ya que también podrás leer una dirección o un puerto determinados. Esta situación permite crear un pequeño «espía» que observe lo que ocurre en un lugar específico. Por esta razón, siempre debemos cifrar y codificar correctamente las cosas. Pero como aquí lo estamos usando todo únicamente con fines de demostración, el riesgo de que se filtre algo se reduce al mínimo y las probabilidades de que algo se escape son prácticamente nulas. Como podrás ver en los vídeos, será posible probar una conexión de red incluso sin tener una instalada.
Pero, dado que todo lo anterior forma parte del MQL5, probablemente estarás satisfecho con los resultados y podrás implementar el servidor. Sin embargo, para quienes no saben cómo implementarlo, veremos este tema en una nueva entrada, únicamente a modo de curiosidad.
Implementar el servidor para el mini chat
Implementar un servidor no es una tarea compleja. En realidad, es bastante sencillo. Ahora bien, implementar un servidor es distinto a ponerlo en funcionamiento. Implementar tiene que ver con crear el código, mientras que ponerlo en funcionamiento implica evitar que se filtre algo a lo que inicialmente no se debería dar acceso.
No trataremos aquí, ni de lejos, la parte relacionada con la puesta en marcha del servidor. Me limitaré únicamente a la parte de implementación. Pero, quiero dejar claro que el código que se mostrará no debe utilizarse con fines de producción. El código mostrado tiene un propósito didáctico. Es decir, no lo utilices sin comprender realmente lo que estás haciendo. Úsalo como una forma de aprender y entender cómo se implementa realmente un servidor. El código completo puede verse a continuación.
001. #define WIN32_LEAN_AND_MEAN 002. 003. #include <winsock2.h> 004. #include <iostream> 005. #include <sstream> 006. #include <stdlib.h> 007. 008. #pragma comment (lib, "Ws2_32.lib") 009. 010. #define DEFAULT_BUFLEN 256 011. #define DEFAULT_PORT 27015 012. 013. using namespace std; 014. 015. int __cdecl main(void) 016. { 017. WSADATA wsData; 018. SOCKET listening, slave; 019. sockaddr_in hint; 020. fd_set master; 021. bool Looping = true; 022. int ConnectCount; 023. string szMsg; 024. 025. if (WSAStartup(MAKEWORD(2, 2), &wsData) != EXIT_SUCCESS) 026. { 027. cout << "Can't Initialize WinSock! Quitting..." << endl; 028. return EXIT_FAILURE; 029. } 030. if ((listening = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) 031. { 032. cerr << "Can't create a socket! Quitting" << endl; 033. WSACleanup(); 034. return EXIT_FAILURE; 035. } 036. else 037. cout << "Creating a Socket..." << endl; 038. 039. hint.sin_family = AF_INET; 040. hint.sin_port = htons(DEFAULT_PORT); 041. hint.sin_addr.S_un.S_addr = INADDR_ANY; 042. 043. if (bind(listening, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR) 044. { 045. cout << "Bind failed with error:" << WSAGetLastError() << endl; 046. WSACleanup(); 047. return EXIT_FAILURE; 048. } 049. else 050. cout << "Bind success..." << endl; 051. 052. if (listen(listening, SOMAXCONN) == SOCKET_ERROR) 053. { 054. cout << "Listen failed with error:" << WSAGetLastError() << endl; 055. WSACleanup(); 056. return EXIT_FAILURE; 057. } 058. else 059. cout << "Listen success..." << endl; 060. 061. FD_ZERO(&master); 062. FD_SET(listening, &master); 063. cout << "Waiting for client connection" << endl; 064. 065. while (Looping) 066. { 067. fd_set tmp = master; 068. 069. ConnectCount = select(0, &tmp, nullptr, nullptr, nullptr); 070. for (int c0 = 0; c0 < ConnectCount; c0++) 071. { 072. if ((slave = tmp.fd_array[c0]) == listening) 073. { 074. SOCKET NewClient = accept(listening, nullptr, nullptr); 075. 076. szMsg = "Sent by SERVER: Welcome to Mini Chart\r\n"; 077. FD_SET(NewClient, &master); 078. send(NewClient, szMsg.c_str(), (int)szMsg.size() + 1, 0); 079. cout << "Client #" << NewClient << " connecting..." << endl; 080. } 081. else 082. { 083. char buff[DEFAULT_BUFLEN]; 084. int bytesIn; 085. 086. ZeroMemory(buff, DEFAULT_BUFLEN); 087. if ((bytesIn = recv(slave, buff, DEFAULT_BUFLEN, 0)) <= 0) 088. { 089. closesocket(slave); 090. cout << "Client #" << slave << " disconnected..." << endl; 091. FD_CLR(slave, &master); 092. } 093. else 094. { 095. if (buff[0] == '\\') //Check command ... 096. { 097. szMsg = string(buff, bytesIn); 098. 099. if (szMsg == "\\shutdown") 100. { 101. Looping = false; 102. break; 103. } 104. continue; 105. } 106. for (u_int c1 = 0; c1 < master.fd_count; c1++) 107. { 108. SOCKET out = master.fd_array[c1]; 109. 110. if ((out != listening) && (out != slave)) 111. { 112. ostringstream s1; 113. 114. s1 << "Client #" << slave << ": " << buff << "\r\n"; 115. send(out, s1.str().c_str(), (int)s1.str().size() + 1, 0); 116. } 117. } 118. } 119. 120. } 121. } 122. } 123. 124. FD_CLR(listening, &master); 125. closesocket(listening); 126. szMsg = "Server is shutting down. Goodbye\r\n"; 127. while (master.fd_count > 0) 128. { 129. slave = master.fd_array[0]; 130. send(slave, szMsg.c_str(), (int)szMsg.size() + 1, 0); 131. FD_CLR(slave, &master); 132. closesocket(slave); 133. } 134. 135. WSACleanup(); 136. 137. return EXIT_SUCCESS; 138. }
Código fuente del servicio
Un detalle sobre este código del servidor: aunque está escrito en C++, deberás compilarlo con las herramientas de Visual Studio. No obstante, si comprendes su funcionamiento, podrás modificarlo hasta el punto de poder compilarlo con GCC. Pero no te centres demasiado en este código, procura entender su funcionamiento, porque eso es lo realmente importante.
Entonces, explicaré algunos puntos principales, especialmente para quienes no están familiarizados con C++ ni con la programación de sockets en C/C++. La primera línea, al igual que la línea ocho, está relacionada con el compilador de Visual Studio. La línea 10 define el tamaño del búfer que se utilizará para leer el socket. Puedes usar un buffer de mayor o menor tamaño, pero ten en cuenta que, si el buffer es demasiado pequeño, será necesario realizar varias lecturas para obtener toda la información del socket. En cualquier caso, para lo que estamos haciendo, que tiene un propósito únicamente didáctico, un buffer de 256 caracteres es suficiente.
La línea 11, en cambio, sí es importante, ya que define el puerto que se utilizará. Podríamos hacerlo de otra manera, pasando el valor como argumento en la línea de comandos, pero la idea aquí es mantenerlo todo lo más simple posible para que la explicación sea clara y no haya dificultades. Muy bien, entre las líneas 17 y 23 declaro algunas variables que se utilizarán de forma más constante. Aunque he incluido todo el código dentro de la función main, lo ideal sería dividirlo en pequeñas rutinas. Sin embargo, como este servidor es muy sencillo, podemos permitirnos hacerlo todo dentro del procedimiento main.
Ahora observa cómo, en cada paso dado para configurar el socket y permitir que el servidor pueda escuchar lo que ocurre en un puerto con el fin de establecer una conexión con algún cliente, se muestra un mensaje en la consola. Así, cuando se crea el socket, en la línea 37 se muestra la confirmación de esta información en la consola. Observa que necesitamos ejecutar dos pasos para lograrlo. El primero es usar la llamada WSAStartup y el segundo es la llamada socket propiamente dicha. Sistemas como Linux no necesitan utilizar la llamada WSAStartup, y algunos modelos de implementación de sockets en Windows tampoco la usan. Sin embargo, en la mayoría de los casos, es recomendable utilizar la llamada WSAStartup cuando se trabaja en Windows
En cualquier caso, se creará el socket. Entre las líneas 39 y 41 configuramos sus parámetros. Esta configuración indica qué puerto se usará, qué dirección se observará y el tipo de socket que se utilizará. Como aquí estamos haciendo pruebas con el socket, permitiremos que cualquier dirección se conecte a él. Pero, es posible definir una dirección específica si se desea. No obstante, en los servidores normalmente se deja habilitado para cualquier conexión. De nuevo, es fundamental saber configurar esta parte, ya que, si no se hace correctamente, se podrían abrir las puertas del infierno. Una vez hecho esto, necesitamos indicar al sistema cómo se configurará el socket y esto se realiza en la línea 43
En la línea 52 configuramos el número máximo de conexiones abiertas simultáneamente que el servidor aceptará. Este paso no tiene ninguna complicación. A partir de este punto, el servidor estará configurado y listo para aceptar conexiones. Entonces, debemos preparar algunas cosas para poder hacer un seguimiento de las conexiones que se producirán. Esto se hace en las líneas 61 y 62. La idea es crear una matriz dinámica que contendrá todas las conexiones abiertas para que el servidor pueda trabajar, como se ve en los vídeos de demostración.
Una vez hecho esto, entramos en el bucle de la línea 65. Este bucle podrá ser terminado por el cliente, como se ve en el vídeo de demostración. Pero, hay algunas cosas que quiero explicar dentro de este bucle. La mayor parte se refiere a leer lo que un cliente ha publicado en el socket y retransmitirlo a todos los demás clientes, por lo que no es tan importante.
Sin embargo, durante la explicación del código en MQL5, mencioné que es importante incluir un carácter de retorno de carro o de nueva línea, ya que el cliente no los incluía en el mensaje, pues era el servidor quien los añadía. Ahora que estamos viendo el código del servidor, fíjate en la línea 114, donde colocamos esos caracteres necesarios para que el cliente de MQL5 sepa que ha recibido el mensaje completo. Si esto no se hace aquí, en el servidor, el minichat hecho en MQL5 no funcionará correctamente.
Esta es la parte que deberás cuidar si implementas tu propio servidor. Debes garantizar que el servidor añada esos mismos caracteres. El orden no importa, pero deben ir al final del mensaje.
Ahora viene la parte realmente interesante. Un poco más arriba expliqué, y se puede ver en el vídeo de demostración, que el cliente puede apagar el servidor. ¿Pero cómo ocurre esto? Es sencillo. Observa la línea 95. En ella indicamos que habrá un carácter al principio del mensaje que señalará un comando para que el servidor lo ejecute. Si esta comprobación tiene éxito, analizaremos el comando y el mensaje publicado no se retransmitirá a otros clientes conectados. Ahora, en la línea 99, probamos uno de esos comandos. En este caso, es sensible a mayúsculas y minúsculas, por lo que el comando debe escribirse exactamente como lo espera el servidor. Si es así, en la línea 101 indicaremos al servidor que finalice sus actividades y cierre todas las conexiones abiertas.
¿Ves qué sencillo es? Aun así, debes tener en cuenta que este es un código con fines didácticos, ya que cualquier cliente puede enviar el comando que será ejecutado por el servidor. En un servidor real habría niveles de autenticación para impedirlo, además de otras precauciones que aquí no son necesarias.
Consideraciones finales
En este artículo concluimos la presentación de cómo puedes crear un minichat utilizando programación externa y MQL5. Su propósito es únicamente didáctico, aunque se puede modificar, mejorar e incluso usar entre amigos para desarrollar algo mucho más profesional. Aprovecha bien este conocimiento. En el anexo encontrarás el código MQL5 del minichat listo y compilado. Todo lo que necesitarás hacer es crear el servidor, lo cual prefiero dejar a tu criterio, ya que esto implica abrir puertos en tu ordenador. El programa utilizado en el vídeo, junto con el minichat, es PuTTY.
| Archivo | Descripción |
|---|---|
| Experts\Expert Advisor.mq5 | Demuestra la interacción entre Chart Trade y el Asesor Experto (Es necesario el Mouse Study para la interacción) |
| Indicators\Chart Trade.mq5 | Crea la ventana para configurar la orden a ser enviada (es necesario el Mouse Study para la interacción) |
| Indicators\Market Replay.mq5 | Crea los controles para la interacción con el servicio de reproducción/simulador (es necesario el Mouse Study para la interacción) |
| Indicators\Mouse Study.mq5 | Permite la interacción entre los controles gráficos y el usuario (Necesario tanto para operar el sistema de repetición como en el mercado real) |
| Servicios\Market Replay.mq5 | Crea y mantiene el servicio de reproducción y simulación de mercado (archivo principal de todo el sistema) |
| Código VS C++\Server.cpp | Crea y mantiene un socket servidor desarrollado en C++ (versión MiniChat). |
| Code in Python\Server.py | Crea y mantiene un socket en Python para la comunicación entre MetaTrader 5 e o Excel |
| Scripts\CheckSocket.mq5 | Permite realizar una prueba de conexión con un socket externo. |
| Indicators\Mini Chat.mq5 | Permite implementar un minichat mediante un indicador (requiere el uso de un servidor para funcionar). |
| Experts\Mini Chat.mq5 | Permite implementar un mini chat mediante un asesor experto (requiere el uso de un servidor para funcionar) |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/12673
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Análisis de todas las variantes del movimiento de precios en una computadora cuántica IBM
Redefiniendo los indicadores de MQL5 y MetaTrader 5
Simulación de mercado (Parte 10): Sockets (IV)
Cómo funciones centenarias pueden actualizar nuestras estrategias comerciales
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso