본문 바로가기

backup

mt4와 FIX프로토콜 연결 : C# Named pipe(명명된파이프) 통신

서로 다른 브로커의 mt4를 연결하거나 여러가지 api들과 mt4를 연결하기 위해서 많은 시도들을 하는데, 지금까지 여러가지 방법을 써본중에 가장 좋은 것은 명명된파이프 통신을 통해서 연결하는 것이 오류나 속도면에서 가장 안정적인 것 같다.

 

mt4와 다른 api와 연결하는 가장 쉬운 방법은 file을 통해서 하는 것인데, 이 경우 속도도 속도지만, 서로 파일을 읽고, 쓰는 경쟁때문에 오류가 자주 발생을 한다. 조금 더 나아간 방법으로 safefilehandling을 쓰는 경우도 있다. 이것도 상당히 훌륭한 방법이지만, 가끔 오류가 발생을 하여서 기회를 잃어버리거나 손실의 가능성이 생긴다. DDE같은 것은 성능이 너무 느리므로 고려 대상은 아닌 것 같다.

 

맨날 네트워크를 다룬다고 하면 TCP/IP만 다루다가, 파이프통신을 구현한다고 며칠을 고생을 하면서 여러가지 상황에 쓸 수 있도록 구축을 하였는데, 안하던거 새로 하려니 엄청 삽질을 많이 했다. ㅋ 덕분에 mt4와 FIX를 연결한 차익거래 시스템을 구현을 하였고, 아직까지 테스트 결과는 만족스럽긴 하다.

 

 

 

 

 

 

파이프통신을 구축을 하면서 여러가지 삽질을 많이 하였는데, 대표적으로 msdn에서 나오는 파이프통신 예제는 파이프를 하나만 해놓고 있는데, 끊임없이 서버와 클라이언트간에 통신을 하려면 파이프 라인을 2개로 하여서 하나는 읽기, 하나는 보내기로 써야 한다. Thread에서 while문을 돌릴 때, 읽기 대기나 쓰기 대기가 걸리면 새로운 정보가 와도 처리를 하지 못하게 된다.

 

그리고 다른 한가지 주의할 사항은 강제적으로 클라이언트에서 끊어져도, 읽기 쓰레드는 끊어진 것을 인지하여도 쓰기 쓰레드는 인지하지 못하였다. 그리고 읽기 쓰레드가 끊어지는 경우 쓰기 쓰레드도 끊어지도록 처리를 하였다.

 

파이프 통신을 이용하면 중간에 메인 서버에서 쉽게 mt4들 다수를 제어할 수 있게 되고, 이는 전문 트레이더의 거래를 쉽게 복사를 하여서 자동으로 거래를 한다든지, 전체 계좌관리를 한번에 다 할 수 있게된다든지 사용할 수 있는 가능성이 많다. 그리고 더 나아가 FIX를 주지 않고, mt4만 제공한다든지, 자체 API를 제공하는 다양한 브로커들을 쉽게 하나로 엮어서 새로운 거래 기회들을 만들어 낼 수 있게 된다.

 

c# dll과 mt4연결에 대한 것은 여기를 참조 http://pepic.tistory.com/132

 

다음은 MSDN에서 나오는 명명된 파이프 구현 코드

 

Server

 

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;

public class PipeServer
{
    private static int numThreads = 4;

    public static void Main()
    {
        int i;
        Thread[] servers = new Thread[numThreads];

        Console.WriteLine("\n*** Named pipe server stream with impersonation example ***\n");
        Console.WriteLine("Waiting for client connect...\n");
        for (i = 0; i < numThreads; i++)
        {
            servers[i] = new Thread(ServerThread);
            servers[i].Start();
        }
        Thread.Sleep(250);
        while (i > 0)
        {
            for (int j = 0; j < numThreads; j++)
            {
                if (servers[j] != null)
                {
                    if (servers[j].Join(250))
                    {
                        Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId);
                        servers[j] = null;
                        i--;    // decrement the thread watch count
                    }
                }
            }
        }
        Console.WriteLine("\nServer threads exhausted, exiting.");
    }

    private static void ServerThread(object data)
    {
        NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads);

        int threadId = Thread.CurrentThread.ManagedThreadId;

        // Wait for a client to connect
        pipeServer.WaitForConnection();

        Console.WriteLine("Client connected on thread[{0}].", threadId);
        try
        {
            // Read the request from the client. Once the client has
            // written to the pipe its security token will be available.

            StreamString ss = new StreamString(pipeServer);

            // Verify our identity to the connected client using a
            // string that the client anticipates.

            ss.WriteString("I am the one true server!");
            string filename = ss.ReadString();

            // Read in the contents of the file while impersonating the client.
            ReadFileToStream fileReader = new ReadFileToStream(ss, filename);

            // Display the name of the user we are impersonating.
            Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.",
                filename, threadId, pipeServer.GetImpersonationUserName());
            pipeServer.RunAsClient(fileReader.Start);
        }
        // Catch the IOException that is raised if the pipe is broken
        // or disconnected.
        catch (IOException e)
        {
            Console.WriteLine("ERROR: {0}", e.Message);
        }
        pipeServer.Close();
    }
}

// Defines the data protocol for reading and writing strings on our stream
public class StreamString
{
    private Stream ioStream;
    private UnicodeEncoding streamEncoding;

    public StreamString(Stream ioStream)
    {
        this.ioStream = ioStream;
        streamEncoding = new UnicodeEncoding();
    }

    public string ReadString()
    {
        int len = 0;

        len = ioStream.ReadByte() * 256;
        len += ioStream.ReadByte();
        byte[] inBuffer = new byte[len];
        ioStream.Read(inBuffer, 0, len);

        return streamEncoding.GetString(inBuffer);
    }

    public int WriteString(string outString)
    {
        byte[] outBuffer = streamEncoding.GetBytes(outString);
        int len = outBuffer.Length;
        if (len > UInt16.MaxValue)
        {
            len = (int)UInt16.MaxValue;
        }
        ioStream.WriteByte((byte)(len / 256));
        ioStream.WriteByte((byte)(len & 255));
        ioStream.Write(outBuffer, 0, len);
        ioStream.Flush();

        return outBuffer.Length + 2;
    }
}

// Contains the method executed in the context of the impersonated user
public class ReadFileToStream
{
    private string fn;
    private StreamString ss;

    public ReadFileToStream(StreamString str, string filename)
    {
        fn = filename;
        ss = str;
    }

    public void Start()
    {
        string contents = File.ReadAllText(fn);
        ss.WriteString(contents);
    }
}

------------------------------------------------------------------------------

 

Client

 

 

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Security.Principal;
using System.Diagnostics;
using System.Threading;

public class PipeClient
{
    private static int numClients = 4;

    public static void Main(string[] Args)
    {
        if (Args.Length > 0)
        {
            if (Args[0] == "spawnclient")
            {
                NamedPipeClientStream pipeClient =
                    new NamedPipeClientStream(".", "testpipe",
                        PipeDirection.InOut, PipeOptions.None,
                        TokenImpersonationLevel.Impersonation);

                Console.WriteLine("Connecting to server...\n");
                pipeClient.Connect();

                StreamString ss = new StreamString(pipeClient);
                // Validate the server's signature string
                if (ss.ReadString() == "I am the one true server!")
                {
                    // The client security token is sent with the first write.
                    // Send the name of the file whose contents are returned
                    // by the server.
                    ss.WriteString("c:\\textfile.txt");

                    // Print the file to the screen.
                    Console.Write(ss.ReadString());
                }
                else
                {
                    Console.WriteLine("Server could not be verified.");
                }
                pipeClient.Close();
                // Give the client process some time to display results before exiting.
                Thread.Sleep(4000);
            }
        }
        else
        {
            Console.WriteLine("\n*** Named pipe client stream with impersonation example ***\n");
            StartClients();
        }
    }

    // Helper function to create pipe client processes
    private static void StartClients()
    {
        int i;
        string currentProcessName = Environment.CommandLine;
        Process[] plist = new Process[numClients];

        Console.WriteLine("Spawning client processes...\n");

        if (currentProcessName.Contains(Environment.CurrentDirectory))
        {
            currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty);
        }

        // Remove extra characters when launched from Visual Studio
        currentProcessName = currentProcessName.Replace("\\", String.Empty);
        currentProcessName = currentProcessName.Replace("\"", String.Empty);

        for (i = 0; i < numClients; i++)
        {
            // Start 'this' program but spawn a named pipe client.
            plist[i] = Process.Start(currentProcessName, "spawnclient");
        }
        while (i > 0)
        {
            for (int j = 0; j < numClients; j++)
            {
                if (plist[j] != null)
                {
                    if (plist[j].HasExited)
                    {
                        Console.WriteLine("Client process[{0}] has exited.",
                            plist[j].Id);
                        plist[j] = null;
                        i--;    // decrement the process watch count
                    }
                    else
                    {
                        Thread.Sleep(250);
                    }
                }
            }
        }
        Console.WriteLine("\nClient processes finished, exiting.");
    }
}

// Defines the data protocol for reading and writing strings on our stream
public class StreamString
{
    private Stream ioStream;
    private UnicodeEncoding streamEncoding;

    public StreamString(Stream ioStream)
    {
        this.ioStream = ioStream;
        streamEncoding = new UnicodeEncoding();
    }

    public string ReadString()
    {
        int len;
        len = ioStream.ReadByte() * 256;
        len += ioStream.ReadByte();
        byte[] inBuffer = new byte[len];
        ioStream.Read(inBuffer, 0, len);

        return streamEncoding.GetString(inBuffer);
    }

    public int WriteString(string outString)
    {
        byte[] outBuffer = streamEncoding.GetBytes(outString);
        int len = outBuffer.Length;
        if (len > UInt16.MaxValue)
        {
            len = (int)UInt16.MaxValue;
        }
        ioStream.WriteByte((byte)(len / 256));
        ioStream.WriteByte((byte)(len & 255));
        ioStream.Write(outBuffer, 0, len);
        ioStream.Flush();

        return outBuffer.Length + 2;
    }
}