본문 바로가기
  • 우당탕탕속의 잔잔함
Programming/Error, Language, Environments

[C#] Redirection을 이용한 Communication 방법

by zpstls 2023. 2. 17.
반응형

이번 포스트는 Redirection을 이용하여,

Process의 출력 스트림을 다른 프로그램이 읽을 수 있도록 하는 방법에 대해 다루고자 합니다.

 

 

먼저, Redirection에 대해 아주 짧게 설명하도록 하겠습니다.

Redirection은 표준 스트림의 흐름을 바꿔주는 기법을 의미합니다. 이 기법은 주로 명령어 표준 출력을 화면이 아닌 파일에 쓸 때 많이 사용합니다. 보통 리눅스에서는 ls > files.txt와 같은 형태로 사용하곤 합니다.

그러나 이 기법을 조금 다른 방식으로 활용할 수 있습니다. 바로, 두 Process 간 통신을 수행할 때 활용하는 것입니다.

 

부모 프로그램이 어떤 자식 프로그램을 실행시키고 이 자식 프로그램이 출력하는 String Data를 계속 모니터링하는 것입니다. 자식 프로그램은 C# 콘솔앱, 부모 프로그램은 Unity로 구현해보도록 할 것입니다.

각각은 다음과 같은 방식을 통해 구현할 수 있습니다.

 

먼저, 자식 프로그램은 다음과 같이 구현합니다.

namespace ConsoleApp
{
    class Child_Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            while (true) {
                Console.WriteLine($"{i}");
                i++;
            }
        }
    }
}

아주 단순합니다. 그냥 무한 루프를 돌면서 1씩 증가하는 값만 Console에 찍어줍니다.

 

다음으로는 부모 프로그램을 다음과 같이 구현합니다.

public class Processor_Controller : MonoBehaviour {
    public int NumberOfChild;
    private Thread[] protocolThread;
    private bool[] m_thread;
    private System.Diagnostics.Process[] p;

    private void Awake() {
        m_thread = new bool[NumberOfChild];
        protocolThread = new Thread[NumberOfChild];
        p = new System.Diagnostics.Process[NumberOfChild];
    }

    void Start() {
        for (int i = 0; i < NumberOfChild; i++) {
            m_thread[i] = true;

            p[i] = new System.Diagnostics.Process();
            p[i].StartInfo.UseShellExecute = false;
            p[i].StartInfo.RedirectStandardOutput = true;
            string eOut = null;
            p[i].StartInfo.RedirectStandardError = true;
            p[i].ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler((sender, e) => { eOut += e.Data; });
            p[i].StartInfo.FileName = System.Environment.CurrentDirectory + i.ToString() + "/Child_" + i.ToString() + ".exe";
            p[i].StartInfo.Arguments = i.ToString();
            p[i].Start();

            p[i].BeginErrorReadLine();

            int idx = i;
            StartCoroutine(StartThread(idx));
        }
    }

    IEnumerator StartThread(int idx) {
        // create thread
        protocolThread[idx] = new Thread(() => { ReadDataThread(idx); });
        yield return new WaitForSeconds(.1f);
        protocolThread[idx].Start();
    }

    private void ReadDataThread(int idx) {
        while (m_thread[idx]) {
            string output = p[idx].StandardOutput.ReadLine();
            if (output != null) {
                Debug.Log("Data >>" + output);
            }            
        }
    }

    void Update() {
    }

    public void QuitThread() {
        for(int i=0; i<NumberOflidar; i++) {
            // exit thread
            //protocolThread[i].Abort();
            protocolThread[i] = null;
            m_thread[i] = false;
        }
    }

    void OnDestroy() {
        QuitThread();
    }
}

Child는 Thread를 통해 관리할 것입니다. 따라서 생성할 Child Program의 수에 맞게 Thread 객체를 생성합니다.

Parent 프로그램이 실행되면, System.Diagnostics.Process를 이용해 Child Program을 실행시켜 줍니다. 그리고 Thread에서 StandardOutput을 통해 String Data를 Read 합니다.

 

Parent에서 Child Process를 실행할 때 중요한 것은 UseShellExecuteRedirectStandardOutput Parameter를 잘 설정해 주는 것입니다. Child는 Output Stream을 Parent에게 보내주어야 하기 때문에 Shell이 생성되어서는 안 되며, Redirection을 사용해야 하므로 해당 Parameter를 True로 해주어야 합니다.

 

 

이게 끝입니다. 아주 간단하죠?

위와 같은 방식은 개별 프로그램이 단방향 통신을 해야 하는데, Socket이나 Serial을 이용하기 어려울 때, 또는 더 간단하게 구현하고자 할 때 사용하면 좋지 않을까 싶습니다.

 

이번 포스트는 이렇게 간단히 마무리하도록 하겠습니다.

반응형

댓글