From feb0192b113770a8a5d10871c4abd0aba655b511 Mon Sep 17 00:00:00 2001 From: Viacheslav Demydiuk Date: Thu, 4 Jan 2024 18:35:15 +0200 Subject: [PATCH] Added new projects of new RPC core: MyClient and MtServivce --- MtClient/MtClient.csproj | 9 + MtClient/MtMessage.cs | 180 +++++++++++++++++ MtClient/MtRpcClient.cs | 184 +++++++++++++++++ MtService/CommandTask.h | 27 +++ MtService/LogConfigurator.cpp | 90 +++++++++ MtService/LogConfigurator.h | 37 ++++ MtService/LogLevel.h | 11 ++ MtService/Logger.cpp | 90 +++++++++ MtService/Logger.h | 22 +++ MtService/MetaTraderHandler.h | 8 + MtService/MtConnection.cpp | 164 ++++++++++++++++ MtService/MtConnection.h | 49 +++++ MtService/MtExpert.cpp | 73 +++++++ MtService/MtExpert.h | 38 ++++ MtService/MtMessage.h | 204 +++++++++++++++++++ MtService/MtServer.cpp | 295 ++++++++++++++++++++++++++++ MtService/MtServer.h | 66 +++++++ MtService/MtService.cpp | 224 +++++++++++++++++++++ MtService/MtService.h | 32 +++ MtService/MtService.vcxproj | 175 +++++++++++++++++ MtService/MtService.vcxproj.filters | 78 ++++++++ MtService/framework.h | 3 + MtService/pch.cpp | 5 + MtService/pch.h | 14 ++ 24 files changed, 2078 insertions(+) create mode 100755 MtClient/MtClient.csproj create mode 100755 MtClient/MtMessage.cs create mode 100755 MtClient/MtRpcClient.cs create mode 100755 MtService/CommandTask.h create mode 100755 MtService/LogConfigurator.cpp create mode 100755 MtService/LogConfigurator.h create mode 100755 MtService/LogLevel.h create mode 100755 MtService/Logger.cpp create mode 100755 MtService/Logger.h create mode 100755 MtService/MetaTraderHandler.h create mode 100755 MtService/MtConnection.cpp create mode 100755 MtService/MtConnection.h create mode 100755 MtService/MtExpert.cpp create mode 100755 MtService/MtExpert.h create mode 100755 MtService/MtMessage.h create mode 100755 MtService/MtServer.cpp create mode 100755 MtService/MtServer.h create mode 100755 MtService/MtService.cpp create mode 100755 MtService/MtService.h create mode 100755 MtService/MtService.vcxproj create mode 100755 MtService/MtService.vcxproj.filters create mode 100755 MtService/framework.h create mode 100755 MtService/pch.cpp create mode 100755 MtService/pch.h diff --git a/MtClient/MtClient.csproj b/MtClient/MtClient.csproj new file mode 100755 index 00000000..bb23fb7d --- /dev/null +++ b/MtClient/MtClient.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/MtClient/MtMessage.cs b/MtClient/MtMessage.cs new file mode 100755 index 00000000..77b09b9a --- /dev/null +++ b/MtClient/MtMessage.cs @@ -0,0 +1,180 @@ +namespace MtClient +{ + public enum MessageType + { + Command = 0, + Response = 1, + Event = 2, + ExpertList = 3, + ExpertAdded = 4, + ExpertRemoved = 5, + } + + public abstract class MtMessage + { + public abstract MessageType MsgType { get; } + + public string Serialize() + { + return $"{(int)MsgType};{GetMessageBody()}"; + } + + protected abstract string GetMessageBody(); + } + + public class MtCommand(int expertHandle, int commandType, int commandId, string payload) : MtMessage + { + public override MessageType MsgType => MessageType.Command; + + public int ExpertHandle { private set; get; } = expertHandle; + public int CommandType { private set; get; } = commandType; + public int CommandId { private set; get; } = commandId; + public string Payload { private set; get; } = payload; + + protected override string GetMessageBody() + { + return $"{ExpertHandle};{CommandId};{CommandType};{Payload}"; + } + } + + public class MtEvent(int expertHandle, int eventType, string payload) : MtMessage + { + public override MessageType MsgType => MessageType.Event; + + public int ExpertHandle { private set; get; } = expertHandle; + public int EventType { private set; get; } = eventType; + public string Payload { private set; get; } = payload; + + protected override string GetMessageBody() + { + throw new NotImplementedException(); + } + + public static MtMessage? Parse(string payload) + { + var pieces = payload.Split(";", 3); + if (pieces.Length == 3 + && int.TryParse(pieces[0], out int expertHandle) + && int.TryParse(pieces[1], out int eventType)) + return new MtEvent(expertHandle, eventType, payload); + return null; + } + } + + public class MtExpertAddedMsg(int expertHandle) : MtMessage + { + public override MessageType MsgType => MessageType.ExpertAdded; + + public int ExpertHandle { private set; get; } = expertHandle; + + protected override string GetMessageBody() + { + throw new NotImplementedException(); + } + + public static MtMessage? Parse(string payload) + { + if (int.TryParse(payload, out int expertHandle) == false) + return null; + + return new MtExpertAddedMsg(expertHandle); + } + } + + public class MtExpertRemovedMsg(int expertHandle) : MtMessage + { + public override MessageType MsgType => MessageType.ExpertRemoved; + + public int ExpertHandle { private set; get; } = expertHandle; + + protected override string GetMessageBody() + { + throw new NotImplementedException(); + } + + public static MtMessage? Parse(string payload) + { + if (int.TryParse(payload, out int expertHandle) == false) + return null; + + return new MtExpertRemovedMsg(expertHandle); + } + } + + public class MtExpertListMsg(List experts) : MtMessage + { + public override MessageType MsgType => MessageType.ExpertList; + + public List Experts { private set; get; } = experts; + + protected override string GetMessageBody() + { + throw new NotImplementedException(); + } + + public static MtMessage? Parse(string payload) + { + var pieces = payload.Split(","); + List handles = []; + foreach (var p in pieces) + { + if (int.TryParse(p, out int expertHandle) == false) + return null; + handles.Add(expertHandle); + } + + return new MtExpertListMsg(handles); + } + } + + public class MtResponse(int expertHandle, int commandId, string payload) : MtMessage + { + public override MessageType MsgType => MessageType.Response; + + public int ExpertHandle { private set; get; } = expertHandle; + public int CommandId { private set; get; } = commandId; + public string Payload { private set; get; } = payload; + + protected override string GetMessageBody() + { + throw new NotImplementedException(); + } + + public static MtMessage? Parse(string payload) + { + var pieces = payload.Split(";", 3); + if (pieces.Length != 3 + || string.IsNullOrEmpty(pieces[0]) + || string.IsNullOrEmpty(pieces[1]) + || string.IsNullOrEmpty(pieces[2])) + return null; + + if (int.TryParse(pieces[0], out int expertHandle) == false) + return null; + + if (int.TryParse(pieces[1], out int commandId) == false) + return null; + + return new MtResponse(expertHandle, commandId, pieces[2]); + } + } + + public static class MtMessageParser + { + static MtMessageParser() + { + msgHandlers_[MessageType.Event] = MtEvent.Parse; + msgHandlers_[MessageType.Response] = MtResponse.Parse; + msgHandlers_[MessageType.ExpertList] = MtExpertListMsg.Parse; + msgHandlers_[MessageType.ExpertAdded] = MtExpertAddedMsg.Parse; + msgHandlers_[MessageType.ExpertRemoved] = MtExpertRemovedMsg.Parse; + } + + public static MtMessage? Parse(MessageType msgType, string payload) + { + return msgHandlers_[msgType](payload); + } + + private static readonly Dictionary> msgHandlers_ = new(); + } +} diff --git a/MtClient/MtRpcClient.cs b/MtClient/MtRpcClient.cs new file mode 100755 index 00000000..a2bbac7e --- /dev/null +++ b/MtClient/MtRpcClient.cs @@ -0,0 +1,184 @@ +using System.Net.WebSockets; +using System.Text; + +namespace MtClient +{ + public class MtRpcClient + { + public MtRpcClient(string host, int port) + { + host_ = host; + port_ = port; + + receiveThread_ = new Thread(new ThreadStart(DoReceive)); + sendThread_ = new Thread(new ThreadStart(DoWrite)); + } + + public async Task Connect() + { + Log($"Connect: started to {host_}:{port_}"); + + try + { + await ws_.ConnectAsync(new Uri($"ws://{host_}:{port_}/ws"), CancellationToken.None); + } + catch (Exception ex) + { + Log($"Connect failed: {ex.Message}"); + throw new Exception($"Failed connection to {host_}:{port_}"); + } + + receiveThread_.Start(); + sendThread_.Start(); + + Log($"Connect: success."); + } + + public async void Disconnect() + { + Log($"Disconnect: {host_}:{port_}"); + + try + { + await ws_.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + ws_.Dispose(); + } + catch (Exception ex) + { + Log($"Disconnect: {ex.Message}"); + } + + sendWaiter_.Set(); + sendThread_.Join(); + receiveThread_.Join(); + + Log($"Disconnect: success"); + } + + public void Send(MtMessage message) + { + pendingMessages_.Enqueue(message.Serialize()); + sendWaiter_.Set(); + } + + private async void DoWrite() + { + while(ws_.State == WebSocketState.Open) + { + string? message = null; + lock(pendingMessages_) + { + if (pendingMessages_.Count > 0) + message = pendingMessages_.Dequeue(); + } + + if (message == null) + { + sendWaiter_.WaitOne(); + continue; + } + + try + { + Log($"DoWrite: sending message: {message}"); + byte[] bytes = Encoding.ASCII.GetBytes(message); + await ws_.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None); + } + catch (Exception e) + { + Log($"DoWrite: {e.Message}"); + } + } + } + + private async void DoReceive() + { + try + { + byte[] recvBuffer = new byte[64 * 1024]; + while (ws_.State == WebSocketState.Open) + { + var result = await ws_.ReceiveAsync(new ArraySegment(recvBuffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Close) + { + Log($"DoReceive: close signal {result.CloseStatusDescription}"); + await ws_.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + break; + } + else + { + var msg = Encoding.ASCII.GetString(recvBuffer, 0, result.Count); + OnReceive(msg); + } + } + } + catch (Exception ex) + { + Log($"Exception in receive - {ex.Message}"); + ConnectionFailed?.Invoke(this, EventArgs.Empty); + } + } + + private void OnReceive(string msg) + { + Log($"OnReceive: {msg}"); + + if (string.IsNullOrEmpty(msg)) + { + Log("OnReceive: Invalid message (null or empty)"); + return; + } + + var pieces = msg.Split(";", 2); + + if (pieces.Length != 2 + || string.IsNullOrEmpty(pieces[0]) + || string.IsNullOrEmpty(pieces[1])) + { + Log("OnReceive: Invalid message format."); + return; + } + + MessageType msgType; + try + { + var msgTypeValue = int.Parse(pieces[0]); + msgType = (MessageType)Enum.ToObject(typeof(MessageType), msgTypeValue); + + } + catch (Exception e) + { + Log($"OnReceive: Parse MessageType failed. {e.Message}"); + return; + } + + var message = MtMessageParser.Parse(msgType, (pieces[1])); + if (message == null) + { + Log("OnReceive: Failed parse message payload"); + return; + } + + MessageReceived?.Invoke(this, message); + } + + private void Log(string msg) + { + Console.WriteLine($"[{Environment.CurrentManagedThreadId}] {msg}"); + } + + public event EventHandler? MessageReceived; + public event EventHandler? ConnectionFailed; + + private readonly ClientWebSocket ws_ = new(); + private readonly string host_; + private readonly int port_; + private readonly byte[] buf_ = new byte[10000]; + private readonly Dictionary> msgHandlers_ = new(); + private readonly Queue pendingMessages_ = []; + + private readonly Thread receiveThread_; + private readonly Thread sendThread_; + private readonly EventWaitHandle sendWaiter_ = new AutoResetEvent(false); + } +} \ No newline at end of file diff --git a/MtService/CommandTask.h b/MtService/CommandTask.h new file mode 100755 index 00000000..14b812ab --- /dev/null +++ b/MtService/CommandTask.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include "MtMessage.h" + +typedef std::function TaskCallback; + +class CommandTask +{ +public: + CommandTask(std::unique_ptr command, TaskCallback callback) + : command_(std::move(command)) + , callback_(std::move(callback)) + {} + + MtCommand& getCommand() const { return *command_; } + + void ProcessResponse(int handle, const std::string& payload) + { + MtResponse respone(handle, command_->getCommandId(), payload); + callback_(respone); + } + +private: + std::unique_ptr command_; + TaskCallback callback_; +}; \ No newline at end of file diff --git a/MtService/LogConfigurator.cpp b/MtService/LogConfigurator.cpp new file mode 100755 index 00000000..6b3f8b77 --- /dev/null +++ b/MtService/LogConfigurator.cpp @@ -0,0 +1,90 @@ +#include "pch.h" +#include "LogConfigurator.h" + +#include +#include +#include + +static const std::string DEFAULT_FILE_EXTENSION = ".log"; + +using namespace boost::log; + +namespace +{ +trivial::severity_level ToSeverityLevel(LogLevel level) +{ + switch (level) + { + case LogLevel::Fatal: + return trivial::fatal; + case LogLevel::Error: + return trivial::error; + case LogLevel::Warning: + return trivial::warning; + case LogLevel::Info: + return trivial::info; + case LogLevel::Debug: + return trivial::debug; + case LogLevel::Trace: + return trivial::trace; + default: + assert(false); + } + throw; +} + +std::string GetTempDirectoryPathImpl() +{ + std::string tmp_prefix; + char char_path[MAX_PATH]; + if (auto s = GetTempPathA(MAX_PATH, char_path)) + { + tmp_prefix = std::string(char_path, std::size_t(s - 1)); + } + std::replace(tmp_prefix.begin(), tmp_prefix.end(), '\\', '/'); + return tmp_prefix; +} +} // namespace + +void LogConfigurator::Setup(LogLevel level, OutputType output_type, + const std::string& profile_name) +{ + if (profile_name.empty()) + throw std::runtime_error("Invalid profile name"); + + static const std::string COMMON_FMT("[%TimeStamp%] [%ThreadID%] [%Severity%] [%ClassName%]: %Message%"); + + register_simple_formatter_factory("Severity"); + + if ((output_type & OutputType::Console) == OutputType::Console) + { + // Output message to console + add_console_log( + std::cout, + keywords::format = COMMON_FMT, + keywords::auto_flush = true); + } + + if ((output_type & OutputType::File) == OutputType::File) + { + std::string file_name = profile_name; + file_name += "_%Y%m%d_%2N"; + file_name += DEFAULT_FILE_EXTENSION; + + std::string file_path = GetTempDirectoryPathImpl() + "\\" + profile_name + "\\" + file_name; + + // Output message to file, rotates when file reached 1mb or at midnight every day. Each log file + // is capped at 1mb and total is 20mb + add_file_log( + keywords::file_name = file_path, + keywords::rotation_size = 1 * 1024 * 1024, + keywords::max_size = 20 * 1024 * 1024, + keywords::time_based_rotation = sinks::file::rotation_at_time_point(0, 0, 0), + keywords::format = COMMON_FMT, + keywords::auto_flush = true); + } + + add_common_attributes(); + + core::get()->set_filter(trivial::severity >= ToSeverityLevel(level)); +} diff --git a/MtService/LogConfigurator.h b/MtService/LogConfigurator.h new file mode 100755 index 00000000..1616c800 --- /dev/null +++ b/MtService/LogConfigurator.h @@ -0,0 +1,37 @@ +#pragma once + +#include "LogLevel.h" +#include +#include + +enum class OutputType : int +{ + Console = 0x1, + File = 0x2 +}; + +inline OutputType operator|(OutputType lhs, OutputType rhs) +{ + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) | static_cast(rhs)); +} + +inline OutputType& operator|=(OutputType& lhs, OutputType rhs) +{ + lhs = lhs | rhs; + return lhs; +} + +inline OutputType operator&(OutputType lhs, OutputType rhs) +{ + using T = std::underlying_type_t; + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + + +class LogConfigurator +{ +public: + static void Setup(LogLevel level, OutputType output_type, + const std::string& profile_name); +}; \ No newline at end of file diff --git a/MtService/LogLevel.h b/MtService/LogLevel.h new file mode 100755 index 00000000..d08266c3 --- /dev/null +++ b/MtService/LogLevel.h @@ -0,0 +1,11 @@ +#pragma once + +enum class LogLevel +{ + Fatal, + Error, + Warning, + Info, + Debug, + Trace +}; \ No newline at end of file diff --git a/MtService/Logger.cpp b/MtService/Logger.cpp new file mode 100755 index 00000000..0cf1102d --- /dev/null +++ b/MtService/Logger.cpp @@ -0,0 +1,90 @@ +#include "pch.h" +#include "Logger.h" +#include "LogLevel.h" +#include "boost/log/common.hpp" +#include "boost/log/trivial.hpp" +#include "boost/log/sources/severity_logger.hpp" +#include + +using namespace boost::log; + +class Logger::LoggerImpl +{ +public: + LoggerImpl(const char* class_name) + { + boost_logger_.add_attribute("ClassName", attributes::constant(class_name)); + } + + void LogSev(trivial::severity_level level, const char* msg) + { + BOOST_LOG_SEV(boost_logger_, level) << msg; + } + + void LogSev(trivial::severity_level level, const char* fmt, va_list va) + { + char buf[8192]; + vsnprintf(buf, sizeof(buf), fmt, va); + LogSev(level, buf); + } + +private: + sources::severity_logger boost_logger_; +}; + +Logger::Logger(const char* class_name) + : logger_impl_(new Logger::LoggerImpl(class_name)) +{ +} + +Logger::~Logger() +{ +} + +void Logger::Fatal(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + logger_impl_->LogSev(trivial::fatal, fmt, va); + va_end(va); +} + +void Logger::Error(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + logger_impl_->LogSev(trivial::error, fmt, va); + va_end(va); +} + +void Logger::Warning(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + logger_impl_->LogSev(trivial::warning, fmt, va); + va_end(va); +} + +void Logger::Info(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + logger_impl_->LogSev(trivial::info, fmt, va); + va_end(va); +} + +void Logger::Debug(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + logger_impl_->LogSev(trivial::debug, fmt, va); + va_end(va); +} + +void Logger::Trace(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + logger_impl_->LogSev(trivial::trace, fmt, va); + va_end(va); +} diff --git a/MtService/Logger.h b/MtService/Logger.h new file mode 100755 index 00000000..ab682b74 --- /dev/null +++ b/MtService/Logger.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +class Logger +{ +public: + Logger(const char* class_name); + ~Logger(); + + void Fatal(const char* fmt, ...); + void Error(const char* fmt, ...); + void Warning(const char* fmt, ...); + void Info(const char* fmt, ...); + void Debug(const char* fmt, ...); + void Trace(const char* fmt, ...); + +private: + class LoggerImpl; + std::unique_ptr logger_impl_; +}; diff --git a/MtService/MetaTraderHandler.h b/MtService/MetaTraderHandler.h new file mode 100755 index 00000000..ed0635af --- /dev/null +++ b/MtService/MetaTraderHandler.h @@ -0,0 +1,8 @@ +#pragma once + +class MetaTraderHandler +{ +public: + virtual ~MetaTraderHandler() = default; + virtual void SendTickToMetaTrader() = 0; +}; \ No newline at end of file diff --git a/MtService/MtConnection.cpp b/MtService/MtConnection.cpp new file mode 100755 index 00000000..14dbeb8c --- /dev/null +++ b/MtService/MtConnection.cpp @@ -0,0 +1,164 @@ +#include "pch.h" +#include "MtConnection.h" + +#include + +MtConnection::MtConnection(WebsocketStream&& ws) + : ws_(std::move(ws)) + , resolver_(ws_.get_executor()) +{ + log_.Debug("%s: connection %p created.", __FUNCTION__, this); +} + +MtConnection::~MtConnection() +{ + log_.Debug("%s: connection %p destroyed.", __FUNCTION__, this); +} + +void MtConnection::Accept() +{ + // Set suggested timeout settings for the websocket + ws_.set_option( + boost::beast::websocket::stream_base::timeout::suggested( + boost::beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(boost::beast::websocket::stream_base::decorator( + [](boost::beast::websocket::response_type& res) + { + res.set(boost::beast::http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-async"); + })); + // Accept the websocket handshake + ws_.async_accept( + std::bind( + &MtConnection::OnAccept, + shared_from_this(), + std::placeholders::_1)); +} + +void MtConnection::Close() +{ + log_.Debug("%s: entry.", __FUNCTION__); + + ws_.async_close(boost::beast::websocket::close_code::normal, + [](boost::beast::error_code const ec){ }); +} + +void MtConnection::Send(const std::string& msg) +{ + log_.Trace("%s: msg = %s", __FUNCTION__, msg.c_str()); + + send_queue_.push(msg); + if (send_queue_.size() == 1) + DoWrite(); +} + +void MtConnection::OnAccept(boost::beast::error_code ec) +{ + log_.Trace("%s: entry.", __FUNCTION__); + + if (ec) + { + log_.Error("%s: %s", __FUNCTION__, ec.what().c_str()); + OnConnectionFailed(ec.message()); + } + else + DoRead(); + + OnConnected(); +} + +void MtConnection::DoRead() +{ + log_.Trace("%s: enter.", __FUNCTION__); + + // Clear the buffer + read_text_.clear(); + + // Read a message into our buffer + ws_.async_read( + read_buffer_, + std::bind( + &MtConnection::OnRead, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + +void MtConnection::OnRead( + boost::beast::error_code ec, + std::size_t bytes_transferred) +{ + boost::ignore_unused(bytes_transferred); + + // This indicates that the session was closed + if (ec == boost::beast::websocket::error::closed) + { + log_.Info("%s: session was closed.", __FUNCTION__); + OnDisconnected(); + return; + } + + if (ec == boost::asio::error::operation_aborted) + { + log_.Info("%s: session was aborted.", __FUNCTION__); + return; + } + + if (ec) + { + log_.Error("%s: %s", __FUNCTION__, ec.message().c_str()); + OnConnectionFailed(ec.message()); + } + else + { + auto msg = boost::beast::buffers_to_string(read_buffer_.data()); + log_.Trace("%s: %s", __FUNCTION__, msg.c_str()); + + OnMessageReceived(msg); + + // Clear the buffer + read_buffer_.consume(read_buffer_.size()); + + boost::asio::dispatch(ws_.get_executor(), + std::bind( + &MtConnection::DoRead, + shared_from_this())); + } +} + +void MtConnection::DoWrite() +{ + if (send_queue_.empty()) + return; + + log_.Trace("%s: msg = %s", __FUNCTION__, send_queue_.front().c_str()); + + ws_.text(ws_.got_text()); + ws_.async_write( + boost::asio::buffer(send_queue_.front()), + std::bind( + &MtConnection::OnWrite, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); +} + +void MtConnection::OnWrite( + boost::beast::error_code ec, + std::size_t bytes_transferred) +{ + boost::ignore_unused(bytes_transferred); + + if (ec) + { + log_.Error("%s, %s", __FUNCTION__, ec.what().c_str()); + return; + } + + send_queue_.pop(); + DoWrite(); +} + diff --git a/MtService/MtConnection.h b/MtService/MtConnection.h new file mode 100755 index 00000000..e27e0dbd --- /dev/null +++ b/MtService/MtConnection.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Logger.h" +#include "MtMessage.h" + +typedef boost::beast::websocket::stream WebsocketStream; + +class MtConnection : public std::enable_shared_from_this +{ +public: + MtConnection(WebsocketStream&& ws); + ~MtConnection(); + + void Accept(); + void Close(); + void Send(const std::string& msg); + + boost::signals2::signal OnConnected; + boost::signals2::signal OnConnectionFailed; + boost::signals2::signal OnDisconnected; + boost::signals2::signal OnMessageReceived; + +private: + void OnAccept(boost::beast::error_code ec); + void DoRead(); + void OnRead( + boost::beast::error_code ec, + std::size_t bytes_transferred); + void DoWrite(); + void OnWrite( + boost::beast::error_code ec, + std::size_t bytes_transferred); + + Logger log_{ "MtConnection" }; + WebsocketStream ws_; + boost::asio::ip::tcp::resolver resolver_; + std::string host_; + std::string read_text_; + boost::beast::flat_buffer read_buffer_; + //std::string send_text_; + std::queue send_queue_; +}; diff --git a/MtService/MtExpert.cpp b/MtService/MtExpert.cpp new file mode 100755 index 00000000..37b0918d --- /dev/null +++ b/MtService/MtExpert.cpp @@ -0,0 +1,73 @@ +#include "pch.h" + +#include "MtExpert.h" +#include + +namespace +{ +constexpr int MT_COMMAND_TYPE_EMPTY = 0; +} + +MtExpert::MtExpert(int handle, std::unique_ptr mt_handler) + : handle_(handle) + , mt_handler_(std::move(mt_handler)) +{ + log_.Debug("%s: expert %d created.", __FUNCTION__, handle_); +} + +MtExpert::~MtExpert() +{ + log_.Debug("%s: expert %d destroyed.", __FUNCTION__, handle_); +} + +void MtExpert::Deinit() +{ + log_.Debug("%s: handle = %d", __FUNCTION__, handle_); + + OnDeinit(handle_); +} + +void MtExpert::SendEvent(int event_type, const std::string& payload) +{ + log_.Trace("%s: handle = %d, event_type = %d, payload = %s", __FUNCTION__, handle_, event_type, payload.c_str()); + + OnEvent(MtEvent(handle_, event_type, payload)); +} + +void MtExpert::SendResponse(const std::string& payload) +{ + log_.Trace("%s: handle = %d, payload = %s", __FUNCTION__, handle_, payload.c_str()); + + if (current_task_) + { + current_task_->ProcessResponse(handle_, payload); + current_task_.reset(); + } +} + +void MtExpert::Process(std::unique_ptr command, TaskCallback callback) +{ + log_.Debug("%s: handle = %d, command type = %d, command id = %d", __FUNCTION__, handle_, + command->getCommandType(), command->getCommandId()); + + auto task = std::make_unique(std::move(command), std::move(callback)); + tasks_.push(std::move(task)); + + mt_handler_->SendTickToMetaTrader(); +} + +int MtExpert::GetCommandType() +{ + if (tasks_.size() == 0) + return MT_COMMAND_TYPE_EMPTY; + + current_task_ = std::move(tasks_.front()); + tasks_.pop(); + + return current_task_->getCommand().getCommandType(); +} + +std::string MtExpert::GetCommandPayload() +{ + return current_task_ ? current_task_->getCommand().getPayload() : ""; +} \ No newline at end of file diff --git a/MtService/MtExpert.h b/MtService/MtExpert.h new file mode 100755 index 00000000..697a119e --- /dev/null +++ b/MtService/MtExpert.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +#include "CommandTask.h" +#include "Logger.h" +#include "MetaTraderHandler.h" +#include "MtMessage.h" + +class MtExpert +{ +public: + MtExpert(int handle, std::unique_ptr mt_handler); + ~MtExpert(); + + void Deinit(); + + void SendEvent(int event_type, const std::string& payload); + void SendResponse(const std::string& payload); + + void Process(std::unique_ptr command, TaskCallback callback); + + int GetCommandType(); + std::string GetCommandPayload(); + + boost::signals2::signal OnEvent; + boost::signals2::signal OnDeinit; + +private: + Logger log_{ "MtExpert" }; + int handle_; + std::unique_ptr mt_handler_; + std::queue> tasks_; + std::unique_ptr current_task_; +}; \ No newline at end of file diff --git a/MtService/MtMessage.h b/MtService/MtMessage.h new file mode 100755 index 00000000..e4154783 --- /dev/null +++ b/MtService/MtMessage.h @@ -0,0 +1,204 @@ +#pragma once + +#include +#include + +const std::string MT_MESSAGE_DELIMETER{ ";" }; + +enum MessageType +{ + COMMAND = 0, + RESPONSE = 1, + EVENT = 2, + EXPERT_LIST = 3, + EXPERT_ADDED = 4, + EXPERT_REMOVED = 5 +}; + +class MtMessage +{ +public: + std::string Serialize() const + { + return std::to_string(GetType()) + MT_MESSAGE_DELIMETER + GetBody(); + } + +protected: + virtual MessageType GetType() const = 0; + virtual std::string GetBody() const = 0; +}; + +class MtResponse : public MtMessage +{ +public: + MtResponse(int expert_handle, int id, const std::string& payload) + : expert_handle_(expert_handle) + , id_(id) + , payload_(payload) + { + } + + int getExpertHandle() const { return expert_handle_; } + + int getCommandId() const { return id_; } + + std::string getPayload() const { return payload_; } + +private: + MessageType GetType() const override + { + return MessageType::RESPONSE; + } + + std::string GetBody() const override + { + return std::to_string(expert_handle_) + + MT_MESSAGE_DELIMETER + std::to_string(id_) + + MT_MESSAGE_DELIMETER + payload_; + } + + int expert_handle_; + int id_; + std::string payload_; +}; + +class MtCommand : public MtMessage +{ +public: + MtCommand(int expert_handle, int command_id, int command_type, const std::string& payload) + : expert_handle_(expert_handle) + , command_id_(command_id) + , command_type_(command_type) + , payload_(payload) + { + } + + int getExpertHandle() const { return expert_handle_; } + + int getCommandId() const { return command_id_; } + + int getCommandType() const { return command_type_; } + + std::string getPayload() const { return payload_; } + +private: + MessageType GetType() const override + { + return MessageType::COMMAND; + } + + std::string GetBody() const override + { + return std::to_string(expert_handle_) + + MT_MESSAGE_DELIMETER + std::to_string(command_id_) + + MT_MESSAGE_DELIMETER + std::to_string(command_type_) + + MT_MESSAGE_DELIMETER + payload_; + } + + int expert_handle_; + int command_id_; + int command_type_; + std::string payload_; +}; + +class MtEvent : public MtMessage +{ +public: + MtEvent(int expert_handle, int event_type, const std::string& payload) + : expert_handle_(expert_handle) + , event_type_(event_type) + , payload_(payload) + { + } + +private: + MessageType GetType() const override + { + return MessageType::EVENT; + } + + std::string GetBody() const override + { + return std::to_string(expert_handle_) + + MT_MESSAGE_DELIMETER + std::to_string(event_type_) + + MT_MESSAGE_DELIMETER + payload_; + } + + int expert_handle_; + int event_type_; + std::string payload_; +}; + +class MtExpertListMsg : public MtMessage +{ +public: + MtExpertListMsg(std::vector experts) + : experts_(std::move(experts)) + { + } + +private: + MessageType GetType() const override + { + return MessageType::EXPERT_LIST; + } + + std::string GetBody() const override + { + std::stringstream ss; + for (size_t i = 0; i < experts_.size(); ++i) + { + if (i != 0) + ss << ","; + ss << experts_[i]; + } + return ss.str(); + } + + int expert_handle_; + std::vector experts_; +}; + +class MtExpertAddedMsg : public MtMessage +{ +public: + MtExpertAddedMsg(int handle) + : handle_(handle) + { + } + +private: + MessageType GetType() const override + { + return MessageType::EXPERT_ADDED; + } + + std::string GetBody() const override + { + return std::to_string(handle_); + } + + int handle_; +}; + +class MtExpertRemovedMsg : public MtMessage +{ +public: + MtExpertRemovedMsg(int handle) + : handle_(handle) + { + } + +private: + MessageType GetType() const override + { + return MessageType::EXPERT_REMOVED; + } + + std::string GetBody() const override + { + return std::to_string(handle_); + } + + int handle_; +}; \ No newline at end of file diff --git a/MtService/MtServer.cpp b/MtService/MtServer.cpp new file mode 100755 index 00000000..b67c6fca --- /dev/null +++ b/MtService/MtServer.cpp @@ -0,0 +1,295 @@ +#include "pch.h" +#include "MtServer.h" + +#include +#include +#include +#include + +#include "MtConnection.h" +#include "MtExpert.h" + +const std::string ADDRESS = "0.0.0.0"; +constexpr int STOP_EXPERT_INTERVAL = 1; // 1 sec + +MtServer::MtServer( + boost::asio::io_context& context, + unsigned short port) + : context_(context) + , port_(port) + , acceptor_(context) + , stop_timer_(context) +{ + boost::beast::error_code ec; + + boost::asio::ip::tcp::endpoint endpoint{boost::asio::ip::make_address(ADDRESS), port}; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if (ec) + { + log_.Error("%s: open failed! %s", __FUNCTION__, ec.what().c_str()); + return; + } + + // Allow address reuse + acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec); + if (ec) + { + log_.Error("%s: set_option failed! %s", __FUNCTION__, ec.what().c_str()); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if (ec) + { + log_.Error("%s: bind failed! %s", __FUNCTION__, ec.what().c_str()); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_listen_connections, ec); + if (ec) + { + log_.Error("%s: listen failed! %s", __FUNCTION__, ec.what().c_str()); + return; + } +} + +MtServer::~MtServer() +{ + log_.Debug("%s: destructor called.", __FUNCTION__); +} + +void MtServer::AddExpert(int handle, MtExpert* expert) +{ + log_.Debug("%s: handle = %d", __FUNCTION__, handle); + + if (experts_.count(handle) == 0) + { + if (experts_.size() == 0) + { + Start(); + } + + auto slot = std::make_unique(); + slot->expert = expert; + slot->event_connection = expert->OnEvent.connect(boost::bind(&MtServer::OnEvent, this, std::placeholders::_1)); + slot->deinit_connection = expert->OnDeinit.connect(boost::bind(&MtServer::OnDeinitExpert, this, std::placeholders::_1)); + experts_[handle] = std::move(slot); + + Send(MtExpertAddedMsg(handle)); + } +} + +void MtServer::DoAccept() +{ + // The new connection gets its own strand + acceptor_.async_accept( + boost::asio::make_strand(context_), + boost::beast::bind_front_handler( + &MtServer::OnAccept, + shared_from_this())); +} + +void MtServer::OnAccept(boost::beast::error_code ec, + boost::asio::ip::tcp::socket socket) +{ + log_.Trace("%s: enter", __FUNCTION__); + + if (ec == boost::asio::error::operation_aborted) + { + log_.Info("%s: session was aborted.", __FUNCTION__); + return; + } + + if (ec) + { + log_.Error("%s: %s", __FUNCTION__, ec.what().c_str()); + } + else + { + WebsocketStream ws(std::move(socket)); + auto connection = std::make_shared(std::move(ws)); + connection->OnConnected.connect([con = connection->weak_from_this(), this]() { + auto connection = con.lock(); + if (connection) + { + auto it = std::find(pending_connections_.begin(), pending_connections_.end(), connection); + if (it != pending_connections_.end()) + { + pending_connections_.erase(it); + connections_.push_back(connection); + log_.Trace("%s: Pushed connection %p to collection", __FUNCTION__, connection.get()); + + std::vector expert_list; + for (const auto& e : experts_) + expert_list.push_back(e.first); + + MtExpertListMsg msg(std::move(expert_list)); + connection->Send(msg.Serialize()); + } + } + }); + connection->OnConnectionFailed.connect([con = connection->weak_from_this(), this](const std::string& msg) { + log_.Warning("MtServer::OnConnectionFailed: %s", msg.c_str()); + auto connection = con.lock(); + if (connection) + { + if (pending_connections_.size() > 0) + { + auto it = std::find(pending_connections_.begin(), pending_connections_.end(), connection); + if (it != pending_connections_.end()) + { + pending_connections_.erase(it); + log_.Trace("MtServer::OnConnectionFailed: Removed connection %p from pending collection", connection.get()); + } + } + if (connections_.size() > 0) + { + auto it = std::find(connections_.begin(), connections_.end(), connection); + if (it != connections_.end()) + { + connections_.erase(it); + log_.Trace("MtServer::OnConnectionFailed: Removed connection %p from collection", connection.get()); + } + } + } + }); + connection->OnDisconnected.connect([con = connection->weak_from_this(), this]() { + auto connection = con.lock(); + if (connection) + { + auto it = std::find(connections_.begin(), connections_.end(), connection); + if (it != connections_.end()) + { + connections_.erase(it); + log_.Trace("MtServer::OnDisconnected: Removed connection %p from collection", connection.get()); + } + } + }); + connection->OnMessageReceived.connect([con = connection->weak_from_this(), this](const std::string& msg) { + log_.Trace("MtServer::OnMessageReceived: %s", msg.c_str()); + auto command = ParseMessage(msg); + if (command) + { + if (experts_.count(command->getExpertHandle()) > 0) + { + experts_[command->getExpertHandle()]->expert->Process(std::move(command), [&](const MtResponse& response) { + auto connection = con.lock(); + if (connection) + connection->Send(response.Serialize()); + else + log_.Warning("MtServer::OnMessageReceived: connection is not valid (null)"); + }); + } + else + { + log_.Warning("MtServer::OnMessageReceived: Expert is not found with handle %d", command->getExpertHandle()); + } + } + else + { + log_.Warning("MtServer::OnMessageReceived: Failed to parse command from message: %s", msg.c_str()); + } + }); + pending_connections_.push_back(connection); + connection->Accept(); + + // Accept another connection + DoAccept(); + } +} + +std::unique_ptr MtServer::ParseMessage(const std::string& msg) +{ + std::unique_ptr command; + + using namespace boost::spirit; + using rule_s = qi::rule; + using rule = qi::rule; + + std::string message_type; + std::string expert_handle; + std::string command_id; + std::string command_type; + std::string payload; + + rule_s word_p = qi::as_string[+(qi::char_ - qi::char_(';'))]; + rule message_type_p = word_p[boost::phoenix::ref(message_type) = qi::_1]; + rule expert_handle_p = +qi::char_(';') >> word_p[boost::phoenix::ref(expert_handle) = qi::_1]; + rule command_id_p = +qi::char_(';') >> word_p[boost::phoenix::ref(command_id) = qi::_1]; + rule command_type_p = +qi::char_(';') >> word_p[boost::phoenix::ref(command_type) = qi::_1]; + rule payload_p = +qi::char_(';') >> word_p[boost::phoenix::ref(payload) = qi::_1]; + + bool ok = qi::parse(msg.begin(), msg.end(), message_type_p >> -expert_handle_p >> -command_id_p >> -command_type_p >> -payload_p); + if (ok) + { + command = std::make_unique(std::stoi(expert_handle), + std::stoi(command_id), std::stoi(command_type), std::move(payload)); + } + + return command; +} + +// Start accepting incoming connections +void MtServer::Start() +{ + log_.Debug("%s: entry. port = %d", __FUNCTION__, port_); + + DoAccept(); +} + +void MtServer::Stop() +{ + log_.Debug("%s: entry. port = %d", __FUNCTION__, port_); + + for(auto& c : connections_) + c->Close(); + + connections_.clear(); + pending_connections_.clear(); + + acceptor_.cancel(); + + OnStopped(port_); +} + +void MtServer::OnEvent(const MtEvent& event) +{ + log_.Trace("%s: entry.", __FUNCTION__); + + Send(event); +} + +void MtServer::OnDeinitExpert(int handle) +{ + log_.Debug("%s: entry. handle = %d", __FUNCTION__, handle); + + if (experts_.count(handle) > 0) + { + log_.Debug("%s: remove expert %d from collection", __FUNCTION__, handle); + experts_.erase(handle); + Send(MtExpertRemovedMsg(handle)); + } + + if (experts_.size() == 0) + { + log_.Debug("%s: expert count = 0. Starting stop timer (1 sec) ...", __FUNCTION__, handle); + stop_timer_.expires_from_now(boost::asio::chrono::seconds(STOP_EXPERT_INTERVAL)); + stop_timer_.async_wait([this](const boost::system::error_code& /*ec*/) { + if (experts_.size() == 0) + Stop(); + }); + } +} + +void MtServer::Send(const MtMessage& message) +{ + auto msg = message.Serialize(); + log_.Trace("%s: %s", __FUNCTION__, msg.c_str()); + for (auto& c : connections_) + c->Send(msg); +} \ No newline at end of file diff --git a/MtService/MtServer.h b/MtService/MtServer.h new file mode 100755 index 00000000..eebb3422 --- /dev/null +++ b/MtService/MtServer.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Logger.h" +#include "MtMessage.h" + +class MtConnection; +class MtExpert; + +struct MtExpertSlot +{ + MtExpert* expert; + boost::signals2::connection event_connection; + boost::signals2::connection deinit_connection; + + ~MtExpertSlot() + { + event_connection.disconnect(); + deinit_connection.disconnect(); + } +}; + +class MtServer : public std::enable_shared_from_this +{ +public: + MtServer( + boost::asio::io_context& context, + unsigned short port); + ~MtServer(); + + void AddExpert(int handle, MtExpert* expert); + + boost::signals2::signal OnStopped; + +private: + void Start(); + void Stop(); + + void DoAccept(); + void OnAccept(boost::beast::error_code ec, + boost::asio::ip::tcp::socket socket); + + std::unique_ptr ParseMessage(const std::string& msg); + + void OnEvent(const MtEvent& event); + void OnDeinitExpert(int handle); + + void Send(const MtMessage& message); + + Logger log_{ "MtServer" }; + boost::asio::io_context& context_; + unsigned short port_; + boost::asio::ip::tcp::acceptor acceptor_; + std::vector> pending_connections_; + std::vector> connections_; + std::unordered_map> experts_; + boost::asio::steady_timer stop_timer_; +}; \ No newline at end of file diff --git a/MtService/MtService.cpp b/MtService/MtService.cpp new file mode 100755 index 00000000..acec9d60 --- /dev/null +++ b/MtService/MtService.cpp @@ -0,0 +1,224 @@ +// MtService.cpp : Defines the functions for the static library. +// + +#include "pch.h" +#include "framework.h" +#include "MtService.h" + +#include "MtExpert.h" +#include "LogConfigurator.h" +#include "MtServer.h" + +#include +#include +#include + +//-------------------------------------------------------------------------------- +// MtService +//-------------------------------------------------------------------------------- + +class MtServiceImpl +{ +public: + MtServiceImpl(); + ~MtServiceImpl(); + + void InitExpert(int port, int handle, std::unique_ptr mt_handler); + void DeinitExpert(int handle); + + void SendEvent(int handle, int event_type, const std::string& payload); + void SendResponse(int handle, const std::string& payload); + + int GetCommandType(int handle); + std::string GetCommandPayload(int handle); + + void LogError(const std::string& error); + +private: + void OnServerStopped(unsigned short port); + void ThreadProc(); + + Logger log_{ "MtService" }; + boost::asio::io_context context_; + std::unique_ptr work_; + std::thread thread_; + + std::unordered_map> experts_; + std::unordered_map> servers_; +}; + +MtServiceImpl::MtServiceImpl() + : work_(new boost::asio::io_context::work(context_)) + , thread_(&MtServiceImpl::ThreadProc, this) +{ + log_.Debug("%s: service created", __FUNCTION__); +} + +MtServiceImpl::~MtServiceImpl() +{ + log_.Trace("%s: destructor started", __FUNCTION__); + + boost::asio::post(context_, [this]() { + if (experts_.size() > 0) + { + for (auto& e : experts_) + e.second->Deinit(); + experts_.clear(); + } + }); + work_.reset(); + if (thread_.joinable()) + thread_.join(); + + log_.Debug("%s: service destroyed", __FUNCTION__); +} + +void MtServiceImpl::InitExpert(int port, int handle, std::unique_ptr mt_handler) +{ + log_.Debug("%s: port = %d, handle = %d", __FUNCTION__, port, handle); + + boost::asio::post(context_, [port, handle, mt_h = std::move(mt_handler), this]() mutable { + auto expert = std::make_unique(handle, std::move(mt_h)); + if (servers_.count(port) == 0) + { + auto server = std::make_shared(context_, port); + server->OnStopped.connect(boost::bind(&MtServiceImpl::OnServerStopped, this, std::placeholders::_1)); + servers_[port] = server; + } + + servers_[port]->AddExpert(handle, expert.get()); + experts_[handle] = std::move(expert); + }); +} + +void MtServiceImpl::DeinitExpert(int handle) +{ + log_.Debug("%s: handle = %d", __FUNCTION__, handle); + + boost::asio::post(context_, [handle, this]() { + if (experts_.count(handle) > 0) + { + experts_[handle]->Deinit(); + experts_.erase(handle); + } + }); +} + +void MtServiceImpl::SendEvent(int handle, int event_type, const std::string& payload) +{ + log_.Trace("%s: handle = %d, event_type = %d, payload = %s", __FUNCTION__, handle, event_type, payload.c_str()); + + boost::asio::post(context_, [handle, event_type, payload = payload, this]() { + if (experts_.count(handle) > 0) + experts_[handle]->SendEvent(event_type, payload); + }); +} + +void MtServiceImpl::SendResponse(int handle, const std::string& payload) +{ + log_.Trace("%s: handle = %d, payload = %s", __FUNCTION__, handle, payload.c_str()); + + boost::asio::post(context_, [handle, payload = payload, this]() { + if (experts_.count(handle) > 0) + experts_[handle]->SendResponse(payload); + }); +} + +int MtServiceImpl::GetCommandType(int handle) +{ + log_.Trace("%s: handle = %d", __FUNCTION__, handle); + + std::packaged_task task([handle, this]() { + return (experts_.count(handle) > 0) ? experts_[handle]->GetCommandType() : 0; + }); + auto f = task.get_future(); + boost::asio::post(context_, std::bind(std::move(task))); + return f.get(); +} + +std::string MtServiceImpl::GetCommandPayload(int handle) +{ + log_.Trace("%s: handle = %d", __FUNCTION__, handle); + + std::packaged_task task([handle, this]() { + return (experts_.count(handle) > 0) ? experts_[handle]->GetCommandPayload() : 0; + }); + auto f = task.get_future(); + boost::asio::post(context_, std::bind(std::move(task))); + return f.get(); +} + +void MtServiceImpl::LogError(const std::string& error) +{ + log_.Error("%s: %s", __FUNCTION__, error.c_str()); +} + +void MtServiceImpl::OnServerStopped(unsigned short port) +{ + log_.Trace("%s: port = %d", __FUNCTION__, port); + + if (servers_.count(port) > 0) + servers_.erase(port); +} + +void MtServiceImpl::ThreadProc() +{ + log_.Debug("%s: started", __FUNCTION__); + context_.run(); + log_.Debug("%s: stopped", __FUNCTION__); +} + +//-------------------------------------------------------------------------------- +// MtService +//-------------------------------------------------------------------------------- + +MtService::MtService() +{ + LogConfigurator::Setup(LogLevel::Trace, OutputType::Console | OutputType::File, "MtApiService"); + impl_ = std::make_unique(); +} + +MtService::~MtService() +{ +} + +MtService& MtService::GetInstance() +{ + static MtService instance; + return instance; +} + +void MtService::InitExpert(int port, int handle, std::unique_ptr mt_handler) +{ + impl_->InitExpert(port, handle, std::move(mt_handler)); +} + +void MtService::DeinitExpert(int handle) +{ + impl_->DeinitExpert(handle); +} + +void MtService::SendEvent(int handle, int event_type, const std::string& payload) +{ + impl_->SendEvent(handle, event_type, payload); +} + +void MtService::SendResponse(int handle, const std::string& payload) +{ + impl_->SendResponse(handle, payload); +} + +int MtService::GetCommandType(int handle) +{ + return impl_->GetCommandType(handle); +} + +std::string MtService::GetCommandPayload(int handle) +{ + return impl_->GetCommandPayload(handle); +} + +void MtService::LogError(const std::string& error) +{ + impl_->LogError(error); +} \ No newline at end of file diff --git a/MtService/MtService.h b/MtService/MtService.h new file mode 100755 index 00000000..c3ba2fdb --- /dev/null +++ b/MtService/MtService.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include "MetaTraderHandler.h" + +class MtServiceImpl; + +class MtService +{ +private: + MtService(); + ~MtService(); + +public: + static MtService& GetInstance(); + + void InitExpert(int port, int handle, std::unique_ptr mt_handler); + void DeinitExpert(int handle); + + void SendEvent(int handle, int event_type, const std::string& payload); + void SendResponse(int handle, const std::string& payload); + + int GetCommandType(int handle); + std::string GetCommandPayload(int handle); + + void LogError(const std::string& error); + +private: + std::unique_ptr impl_; +}; \ No newline at end of file diff --git a/MtService/MtService.vcxproj b/MtService/MtService.vcxproj new file mode 100755 index 00000000..d50e4e44 --- /dev/null +++ b/MtService/MtService.vcxproj @@ -0,0 +1,175 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {3effff03-2a1d-48aa-a49a-9f90889f31e0} + MtService + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + + + + + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + + + + + true + true + true + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + C:\Users\vdemy\source\repos\MtService\MtService;C:\Users\vdemy\source\repos\MtService\thirdparty\include;%(AdditionalIncludeDirectories) + Default + + + + + true + + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + Use + pch.h + + + + + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/MtService/MtService.vcxproj.filters b/MtService/MtService.vcxproj.filters new file mode 100755 index 00000000..bada8a25 --- /dev/null +++ b/MtService/MtService.vcxproj.filters @@ -0,0 +1,78 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/MtService/framework.h b/MtService/framework.h new file mode 100755 index 00000000..880eb720 --- /dev/null +++ b/MtService/framework.h @@ -0,0 +1,3 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers diff --git a/MtService/pch.cpp b/MtService/pch.cpp new file mode 100755 index 00000000..91c22df2 --- /dev/null +++ b/MtService/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/MtService/pch.h b/MtService/pch.h new file mode 100755 index 00000000..37f3b744 --- /dev/null +++ b/MtService/pch.h @@ -0,0 +1,14 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" +#include + +#endif //PCH_H