295 Zeilen
8,3 KiB
Markdown
295 Zeilen
8,3 KiB
Markdown
|
|
# Package sockets
|
||
|
|
|
||
|
|
**TCP socket communication library for MQL5 using Winsock2**
|
||
|
|
*MIT License*
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## What is this?
|
||
|
|
|
||
|
|
`sockets` is a **TCP socket communication library** for MQL5 that wraps the Windows Winsock2 API.
|
||
|
|
|
||
|
|
It provides:
|
||
|
|
|
||
|
|
- `ClientSocket` class for connecting to TCP servers
|
||
|
|
- `ServerSocket` class for creating TCP servers
|
||
|
|
- Non-blocking I/O operations
|
||
|
|
- Message framing with customizable separators
|
||
|
|
- Binary and text data transmission
|
||
|
|
- Error handling without Print() calls
|
||
|
|
- Automatic hostname resolution (DNS)
|
||
|
|
- Support for both 32-bit and 64-bit MetaTrader 5
|
||
|
|
|
||
|
|
This package enables MQL5 programs to communicate over TCP/IP networks, making it possible to integrate Expert Advisors, Scripts, and Services with external applications, databases, and web services.
|
||
|
|
|
||
|
|
## Prerequisites
|
||
|
|
|
||
|
|
To use this project, you will need:
|
||
|
|
|
||
|
|
1. [**MetaTrader 5**](https://www.mql5.com/) installed.
|
||
|
|
2. **KnitPkg CLI**: The KnitPkg package manager for MetaTrader. If you don't have it, you can install it by following the instructions in the [main KnitPkg repository](https://github.com/knitpkg-dev/knitpkg-mt.git).
|
||
|
|
3. **KnitPkg homepage**: See [https://knitpkg.dev](https://knitpkg.dev) for an overview and [https://docs.knitpkg.dev](https://docs.knitpkg.dev) for documentation.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Features
|
||
|
|
|
||
|
|
- **Package type** (`type: package`) — header-only library consumable by other KnitPkg projects
|
||
|
|
- **MQL5 target** — designed for MetaTrader 5
|
||
|
|
- **Compile support** — `kp compile` compiles the unit test script(s)
|
||
|
|
- **TCP client sockets** — connect to remote servers from EAs and Scripts
|
||
|
|
- **TCP server sockets** — create servers in MQL5 Services
|
||
|
|
- **Non-blocking I/O** — all operations are non-blocking for responsive applications
|
||
|
|
- **Message framing** — built-in support for message delimiters (e.g., newline)
|
||
|
|
- **Binary and text data** — send/receive both string and binary data
|
||
|
|
- **Error handling** — retrieve error messages via GetLastError() without Print() calls
|
||
|
|
- **DNS resolution** — automatic hostname to IP address resolution
|
||
|
|
- **Cross-architecture** — works on both 32-bit and 64-bit MT5 terminals
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Installing as a dependency (recommended)
|
||
|
|
|
||
|
|
To add `sockets` to your KnitPkg project:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
kp add @douglasrechia/sockets
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Basic usage (example)
|
||
|
|
|
||
|
|
In the case your MQL5 project uses `include_mode: include`, include headers from the KnitPkg include directory:
|
||
|
|
|
||
|
|
```mql5
|
||
|
|
#include "../knitpkg/include/douglasrechia/sockets/Sockets.mqh"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Client Socket Example
|
||
|
|
|
||
|
|
```mql5
|
||
|
|
void OnStart()
|
||
|
|
{
|
||
|
|
// Connect to server
|
||
|
|
douglasrechia::ClientSocket* client = new douglasrechia::ClientSocket("127.0.0.1", 8080);
|
||
|
|
|
||
|
|
if(!client.IsSocketConnected())
|
||
|
|
{
|
||
|
|
Print("Connection failed: ", client.GetLastError());
|
||
|
|
delete client;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Send a message
|
||
|
|
if(!client.Send("Hello Server\n"))
|
||
|
|
{
|
||
|
|
Print("Send failed: ", client.GetLastError());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Receive response (with newline separator)
|
||
|
|
string response = "";
|
||
|
|
while(response == "" && client.IsSocketConnected())
|
||
|
|
{
|
||
|
|
response = client.Receive("\n");
|
||
|
|
Sleep(10);
|
||
|
|
}
|
||
|
|
|
||
|
|
Print("Server response: ", response);
|
||
|
|
|
||
|
|
// Cleanup
|
||
|
|
delete client;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Server Socket Example
|
||
|
|
|
||
|
|
```mql5
|
||
|
|
douglasrechia::ServerSocket* server;
|
||
|
|
douglasrechia::ClientSocket* clients[];
|
||
|
|
|
||
|
|
void OnStart()
|
||
|
|
{
|
||
|
|
// Create server on port 8080
|
||
|
|
server = new douglasrechia::ServerSocket(8080);
|
||
|
|
|
||
|
|
if(!server.Created())
|
||
|
|
{
|
||
|
|
Print("Server creation failed: ", server.GetLastError());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Print("Server listening on port 8080");
|
||
|
|
|
||
|
|
// Main server loop
|
||
|
|
while(!IsStopped())
|
||
|
|
{
|
||
|
|
// Accept new connections
|
||
|
|
douglasrechia::ClientSocket* newClient = server.Accept();
|
||
|
|
if(newClient != NULL)
|
||
|
|
{
|
||
|
|
Print("New client connected");
|
||
|
|
int size = ArraySize(clients);
|
||
|
|
ArrayResize(clients, size + 1);
|
||
|
|
clients[size] = newClient;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Process existing clients
|
||
|
|
for(int i = ArraySize(clients) - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
string msg = clients[i].Receive("\n");
|
||
|
|
if(msg != "")
|
||
|
|
{
|
||
|
|
Print("Received: ", msg);
|
||
|
|
// Echo back
|
||
|
|
clients[i].Send("Echo: " + msg + "\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove disconnected clients
|
||
|
|
if(!clients[i].IsSocketConnected())
|
||
|
|
{
|
||
|
|
delete clients[i];
|
||
|
|
ArrayRemove(clients, i, 1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Sleep(10);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cleanup
|
||
|
|
for(int i = 0; i < ArraySize(clients); i++)
|
||
|
|
delete clients[i];
|
||
|
|
delete server;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
> Note: The exact method signatures and usage patterns are documented in the header files. Use MetaEditor IntelliSense to browse the available APIs.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Exploring the project locally (optional)
|
||
|
|
|
||
|
|
If you want to explore how a KnitPkg package is structured, clone the repository and run the standard workflow.
|
||
|
|
|
||
|
|
### 1) Clone into MetaTrader `Scripts` folder (example)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Go to your MetaTrader 'Scripts' directory.
|
||
|
|
# Tip: in MetaEditor, right-click the `Scripts` folder and choose "Open Folder".
|
||
|
|
cd "C:\Users\username\AppData\Roaming\MetaQuotes\Terminal\<TERMINAL_ID>\MQL5\Scripts"
|
||
|
|
|
||
|
|
git clone https://forge.mql5.io/DouglasRechia/sockets.git
|
||
|
|
cd sockets
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2) Compile
|
||
|
|
|
||
|
|
```bash
|
||
|
|
kp compile
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Running unit tests
|
||
|
|
|
||
|
|
Unit tests are implemented as a MetaTrader **Script**.
|
||
|
|
|
||
|
|
1. Compile the project.
|
||
|
|
2. Restart MetaTrader (so the Navigator refreshes).
|
||
|
|
3. Run the generated unit test script from the Navigator on any chart.
|
||
|
|
4. Check results in the MetaTrader console (Experts/Scripts tab, depending on your setup).
|
||
|
|
|
||
|
|
The compiled binary is placed under the project `bin/` directory (and appears in the Navigator under the corresponding compiled location after refresh).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## One-command download/build via the registry (optional)
|
||
|
|
|
||
|
|
You can also use `kp get` to query the registry for metadata and automatically download/build the latest stable version.
|
||
|
|
|
||
|
|
Example (run from your MetaTrader *Data Folder* root):
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd "C:\Users\username\AppData\Roaming\MetaQuotes\Terminal\<TERMINAL_ID>"
|
||
|
|
|
||
|
|
kp get mql5 @douglasrechia/sockets
|
||
|
|
```
|
||
|
|
|
||
|
|
Restart MetaTrader afterward to refresh the Navigator, then run the unit test script as described above.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Reference
|
||
|
|
|
||
|
|
### ClientSocket
|
||
|
|
|
||
|
|
#### Constructor
|
||
|
|
```cpp
|
||
|
|
ClientSocket(string serverHost, ushort serverPort)
|
||
|
|
```
|
||
|
|
Creates a client socket and connects to the specified server.
|
||
|
|
|
||
|
|
#### Methods
|
||
|
|
|
||
|
|
| Method | Description |
|
||
|
|
|--------|-------------|
|
||
|
|
| `bool IsSocketConnected()` | Returns true if socket is connected |
|
||
|
|
| `string GetLastError()` | Returns the last error message |
|
||
|
|
| `void ClearLastError()` | Clears the last error message |
|
||
|
|
| `void CloseSocket()` | Closes the socket connection |
|
||
|
|
| `bool Send(string strMsg)` | Sends a UTF-8 string |
|
||
|
|
| `bool Send(uchar& buffer[], int startAt=0, int szToSend=-1)` | Sends binary data |
|
||
|
|
| `string Receive(string MessageSeparator="")` | Receives string data with optional message separator |
|
||
|
|
| `int Receive(uchar& buffer[])` | Receives binary data, returns number of bytes |
|
||
|
|
|
||
|
|
### ServerSocket
|
||
|
|
|
||
|
|
#### Constructor
|
||
|
|
```cpp
|
||
|
|
ServerSocket(ushort serverPort, bool reuse=true)
|
||
|
|
```
|
||
|
|
Creates a server socket listening on the specified port.
|
||
|
|
|
||
|
|
#### Methods
|
||
|
|
|
||
|
|
| Method | Description |
|
||
|
|
|--------|-------------|
|
||
|
|
| `bool Created()` | Returns true if server was created successfully |
|
||
|
|
| `string GetLastError()` | Returns the last error message |
|
||
|
|
| `void ClearLastError()` | Clears the last error message |
|
||
|
|
| `void CloseServer()` | Closes the server socket |
|
||
|
|
| `ClientSocket* Accept()` | Accepts a pending connection, returns NULL if none waiting |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Message Framing
|
||
|
|
|
||
|
|
TCP is stream-oriented, not message-oriented. The `MessageSeparator` parameter solves the message framing problem:
|
||
|
|
|
||
|
|
```cpp
|
||
|
|
// Without separator - returns all buffered data (may be incomplete)
|
||
|
|
string data = client.Receive("");
|
||
|
|
|
||
|
|
// With separator - returns only complete messages
|
||
|
|
string message = client.Receive("\n"); // Waits for newline
|
||
|
|
```
|
||
|
|
|
||
|
|
When using a separator:
|
||
|
|
- Data accumulates in an internal buffer until the separator is found
|
||
|
|
- Returns the first complete message (without the separator)
|
||
|
|
- Remaining data stays buffered for the next call
|
||
|
|
- Returns empty string if message is incomplete
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
This project is released under the **MIT License**. See `LICENSE` for details.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Disclaimer
|
||
|
|
|
||
|
|
This code is provided **as-is**, for **educational purposes only**.
|
||
|
|
No warranty (express or implied) is provided. Use at your own risk.
|