본문 바로가기

C#

[c#] IPC 통신으로 양방향 통신하기

반응형

 IPC 통신으로 양방향 통신하는 방법 입니다.

소켓 통신도 있는데 그것보다 약간더 쉽게 사용할수 있습니다.

 

클로드에게 문의후 내가 약간 고친 소스

클로드가 알려준것 처음 테스트 해보니 에러가 있었습니다.(역시 기능 위주로만 사용을 해야 할거 같습니다.)

 

PC방 관리용으로 만들었습니다.

 

반응형

 

프로그램 기능 2가지

- IPC 통신으로 양방향 통신 (서버, 클라이언트 기능)

- WatchDog 기능으로 서로 프로세스가 죽었는지 체크후 죽으면 바로 실행 시켜주는 기능(A, B 프로그램이 서로 체크)

 

 

WatchDog  로직

// ProcessWatchdog.cs
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.IO.Pipes;

namespace CommonLibrary
{
    /// <summary>
    /// 프로세스 감시 및 자동 재시작을 위한 Watchdog 클래스
    /// </summary>
    public class ProcessWatchdog : IDisposable
    {
        private readonly string _processName;
        private readonly string _processPath;
        private readonly string _watchdogId;
        private readonly string _partnerId;
        private readonly int _checkInterval;
        private readonly int _timeoutThreshold;
        private readonly string _heartbeatDir;
        private CancellationTokenSource _cancellationTokenSource;
        private Task _watchdogTask;
        private bool _isDisposed;

        private NamedPipeServerStream _pipeServer;
        private bool _isPaused = false;
        private readonly string _pipeName_Server;
        private readonly string _pipeName_Client;

        // 이벤트 정의
        public event EventHandler<string> OnControlCommand;
        public event EventHandler<WatchdogEventArgs> OnLogMessage;
        public event EventHandler<WatchdogEventArgs> OnError;

        public class WatchdogEventArgs : EventArgs
        {
            public string Message { get; }
            public bool IsError { get; }

            public WatchdogEventArgs(string message, bool isError = false)
            {
                Message = message;
                IsError = isError;
            }
        }

        /// <summary>
        /// ProcessWatchdog 클래스의 새 인스턴스를 초기화합니다.
        /// </summary>
        /// <param name="processName">감시할 프로세스의 이름 (확장자 제외)</param>
        /// <param name="processPath">감시할 프로세스의 전체 경로</param>
        /// <param name="watchdogId">현재 프로세스의 식별자</param>
        /// <param name="partnerId">파트너 프로세스의 식별자</param>
        /// <param name="checkInterval">상태 확인 간격 (밀리초)</param>
        /// <param name="timeoutThreshold">재시작 타임아웃 임계값 (밀리초)</param>
        public ProcessWatchdog(
            string processName,
            string processPath,
            string watchdogId,
            string partnerId,
            int checkInterval = 2000,
            int timeoutThreshold = 10000)
        {
            _processName = processName;
            _processPath = processPath;
            _watchdogId = watchdogId;
            _partnerId = partnerId;
            _checkInterval = checkInterval;
            _timeoutThreshold = timeoutThreshold;
            _heartbeatDir = Path.Combine(Path.GetTempPath(), "ProcessWatchdog");

            _pipeName_Server = $"Watchdog_{watchdogId}_{partnerId}";
            _pipeName_Client = $"Watchdog_{partnerId}_{watchdogId}";
        }

        protected virtual void RaiseLogMessage(string message)
        {
            OnLogMessage?.Invoke(this, new WatchdogEventArgs(message));
        }

        protected virtual void RaiseError(string error)
        {
            OnError?.Invoke(this, new WatchdogEventArgs(error, true));
            //RaiseLogMessage($"Error: {error}");
        }

        /// <summary>
        /// Watchdog 서비스를 시작합니다.
        /// </summary>
        public async Task StartAsync()
        {
            try
            {
                RaiseLogMessage($"Watchdog 서비스 시작");

                // 하트비트 디렉토리 생성
                if (!Directory.Exists(_heartbeatDir))
                {
                    Directory.CreateDirectory(_heartbeatDir);
                }

                _cancellationTokenSource = new CancellationTokenSource();

                // 자신의 하트비트 전송 시작
                _ = SendHeartbeatAsync(_cancellationTokenSource.Token);

                // IPC 서버 시작
                _ = StartIPCServerAsync();

                // 파트너 프로세스 감시 시작 : 서로 감시하고 살릴일 없으면 사용 안해도 됨
                _watchdogTask = MonitorPartnerAsync(_cancellationTokenSource.Token);

                await Task.CompletedTask;
            }
            catch (Exception ex)
            {
                await Logger.Instance.LogMessageAsync($"[Watchdog] 시작 오류: {ex.Message}");
                throw;
            }
        }

        private async Task SendHeartbeatAsync(CancellationToken cancellationToken)
        {
            string heartbeatFile = Path.Combine(_heartbeatDir, $"heartbeat_{_watchdogId}.txt");

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    // 현재 시간을 파일에 기록
                    File.WriteAllText(heartbeatFile, DateTime.Now.Ticks.ToString());
                    await Task.Delay(_checkInterval / 2, cancellationToken);
                }
                catch (OperationCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    await Logger.Instance.LogMessageAsync($"[Watchdog] 하트비트 전송 오류: {ex.Message}");
                    await Task.Delay(1000, cancellationToken); // 에러 발생시 1초 대기
                }
            }

            // 종료시 정리
            try
            {
                if (File.Exists(heartbeatFile))
                {
                    File.Delete(heartbeatFile);
                }
            }
            catch { }
        }


        private async Task StartIPCServerAsync()
        {
            while (!_cancellationTokenSource.Token.IsCancellationRequested)
            {
                try
                {
                    _pipeServer = new NamedPipeServerStream(_pipeName_Server, PipeDirection.In);

                    await Task.Run(() => _pipeServer.WaitForConnection());
                    RaiseLogMessage($"IPC 서버 시작됨: {_pipeName_Server}");

                    using (var reader = new StreamReader(_pipeServer))
                    {
                        string command = await reader.ReadLineAsync();
                        if (!string.IsNullOrEmpty(command))
                        {
                            RaiseLogMessage($"IPC 명령 수신: {command}");
                            OnControlCommand?.Invoke(this, command); // 받은 명령은 이벤트로 전달해서 OnControlCommand()에서 처리 - 실제 구현하는곳에서 처리하도록

                            //모니터링 일시 중지/재개만 여기서 처리
                            switch (command.ToUpper())
                            {
                                case "PAUSE":
                                    _isPaused = true;
                                    RaiseLogMessage("Watchdog 모니터링이 일시 중지되었습니다.");
                                    break;
                                case "RESUME":
                                    _isPaused = false;
                                    RaiseLogMessage("Watchdog 모니터링이 재개되었습니다.");
                                    break;
                            }
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    RaiseError($"IPC 서버 오류: {ex.Message}");
                    await Task.Delay(1000, _cancellationTokenSource.Token);
                }
                finally
                {
                    try
                    {
                        if (_pipeServer != null)
                        {
                            if (_pipeServer.IsConnected)
                                _pipeServer.Disconnect();
                            _pipeServer.Dispose();
                        }
                    }
                    catch { }

                    // 새로운 연결을 위해 잠시 대기
                    await Task.Delay(100);
                }
            }
        }

        public async Task SendControlCommandAsync(string command)
        {
            NamedPipeClientStream pipeClient = null;
            try
            {
                RaiseLogMessage($"제어 명령 전송 시도: {command} (Pipe: {_pipeName_Client})");

                pipeClient = new NamedPipeClientStream(".", _pipeName_Client, PipeDirection.Out);

                // Connect 시도
                var connectTask = Task.Run(() =>
                {
                    try
                    {
                        pipeClient.Connect(5000); // 5초 타임아웃
                        return true;
                    }
                    catch (Exception)
                    {
                        return false;
                    }
                });

                // 연결 대기
                if (await connectTask)
                {
                    using (var writer = new StreamWriter(pipeClient))
                    {
                        await writer.WriteLineAsync(command);
                        await writer.FlushAsync();
                        RaiseLogMessage($"제어 명령 전송 성공: {command}");
                    }
                }
                else
                {
                    RaiseError($"파이프 연결 실패: {_pipeName_Client}");
                }
            }
            catch (Exception ex)
            {
                RaiseError($"제어 명령 전송 오류: {ex.Message}");
            }
            finally
            {
                try
                {
                    if (pipeClient != null)
                    {
                        if (pipeClient.IsConnected)
                        {
                            pipeClient.Close();
                        }
                        pipeClient.Dispose();
                    }
                }
                catch { }
            }
        }

        private async Task MonitorPartnerAsync(CancellationToken cancellationToken)
        {
            string partnerHeartbeatFile = Path.Combine(_heartbeatDir, $"heartbeat_{_partnerId}.txt");
            DateTime lastCheck = DateTime.Now;

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    // isPaused일 때는 모니터링만 하고 재시작은 하지 않음
                    bool needsRestart = false;

                    if (File.Exists(partnerHeartbeatFile))
                    {
                        string ticksStr = File.ReadAllText(partnerHeartbeatFile);
                        if (long.TryParse(ticksStr, out long ticks))
                        {
                            var lastHeartbeat = new DateTime(ticks);
                            if ((DateTime.Now - lastHeartbeat).TotalMilliseconds > _timeoutThreshold)
                            {
                                needsRestart = true;
                            }
                        }
                        else
                        {
                            needsRestart = true;
                        }
                    }
                    else
                    {
                        // 하트비트 파일이 없는 경우도 재시작 필요
                        if ((DateTime.Now - lastCheck).TotalMilliseconds > _timeoutThreshold)
                        {
                            needsRestart = true;
                        }
                    }

                    if (needsRestart && !_isPaused)  // isPaused 체크를 여기서 함
                    {
                        await RestartPartnerProcessAsync();
                        lastCheck = DateTime.Now;
                    }

                    await Task.Delay(_checkInterval, cancellationToken);
                }
                catch (OperationCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    RaiseError($"파트너 모니터링 오류: {ex.Message}");
                    await Task.Delay(1000, cancellationToken);
                }
            }
        }

        private async Task RestartPartnerProcessAsync()
        {
            try
            {
                bool bIsExistProcess = false;

                RaiseLogMessage($"파트너 프로세스 재시작 시도: {_processName}");

                /*
                // 기존 프로세스 찾기 및 종료
                Process[] processes = Process.GetProcessesByName(_processName);
                foreach (Process process in processes)
                {
                    try
                    {
                        if (!process.HasExited)
                        {
                            process.Kill();
                            await Task.Delay(2000); // 프로세스 종료 대기
                            RaiseLogMessage($"기존 프로세스 종료함 : {_processName}");
                        }
                    }
                    catch (Exception ex)
                    {
                        RaiseError($"프로세스 종료 오류: {ex.Message}");
                    }
                    finally
                    {
                        process.Dispose();
                    }
                }
                */

                // 기존 프로세스 찾기
                Process[] processes = Process.GetProcessesByName(_processName);
                foreach (Process process in processes)
                {
                    if (!process.HasExited)
                    {
                        bIsExistProcess = true;
                        RaiseLogMessage($"기존 프로세스 존재함 : {_processName}");
                        break;
                    }
                }

                if (!bIsExistProcess)
                {
                    RaiseLogMessage($"{_processName} : 프로세스가 없어서 새로 시작합니다.");

                    // 새 프로세스 시작
                    var startInfo = new ProcessStartInfo
                    {
                        FileName = _processPath,
                        UseShellExecute = true,
                        WorkingDirectory = Path.GetDirectoryName(_processPath)
                    };

                    using (Process.Start(startInfo))
                    {
                        await Task.Delay(1000); // 프로세스 시작 대기
                        RaiseLogMessage($"파트너 프로세스가 재시작되었습니다: {_processName}");
                    }
                }
            }
            catch (Exception ex)
            {
                RaiseError($"프로세스 재시작 오류: {ex.Message}");
            }
        }

        /// <summary>
        /// Watchdog 서비스를 중지합니다.
        /// </summary>
        public async Task StopAsync()
        {
            try
            {
                _cancellationTokenSource?.Cancel();
                if (_watchdogTask != null)
                {
                    await _watchdogTask;
                }
            }
            catch (Exception ex)
            {
                await Logger.Instance.LogMessageAsync($"[Watchdog] 중지 오류: {ex.Message}");
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    _cancellationTokenSource?.Cancel();
                    _cancellationTokenSource?.Dispose();
                    _pipeServer?.Dispose();
                }
                _isDisposed = true;
            }
        }
    }
}

 

 

A프로그램

//A 프로그램

public bool _bIsCloseWithWatchdog = true;  //프로그램 종료시 Watchdog 프로그램도 같이 종료하기
InitializeWatchdogAsync();


public async Task InitializeWatchdogAsync()
{
    try
    {
        watchdogProcessPath = @"pc_b.exe";

        _watchdog = new ProcessWatchdog(
            processName: "pc_b",
            processPath: watchdogProcessPath,
            watchdogId: "HostId",
            partnerId: "WatchdogId",
            checkInterval: 2000,   // 2초마다 체크
            timeoutThreshold: 6000 // 6초 이상 응답 없으면 재시작
        );

        // Watchdog 이벤트 핸들러 등록
        _watchdog.OnLogMessage += (s, e) => RaiseLogMessage($"[Watchdog] {e.Message}");
        _watchdog.OnError += (s, e) => RaiseError($"[Watchdog Error] {e.Message}");

        // IPC 명령 수신 이벤트 핸들러 - Watchdog에서 보낸 명령 수신
        _watchdog.OnControlCommand += (s, command) =>
        {
            RaiseLogMessage($"[Watchdog] IPC 명령 수신 : {command}");

            switch (command.ToUpper())
            {
                case "EXIT":
                    //Application.Exit();
                    break;
            }
        };

        await _watchdog.StartAsync();   //Watchdog 서비스를 시작

        RaiseLogMessage("[Watchdog] Watchdog 서비스가 시작되었습니다.");
    }
    catch (Exception ex)
    {
        RaiseError($"[Watchdog Error] Watchdog 초기화 오류: {ex.Message}");
    }
}

// Watchdog에 명령 전송
public async Task SendWatchdogCommand(string strCmd)
{
    if (_watchdog != null)
    {
        await _watchdog.SendControlCommandAsync(strCmd);    // Watchdog에 명령 전송
    }
}

// Watchdog 중지 메서드
public async Task StopWatchdogAsync()
{
    if (_watchdog != null)
    {
        try
        {
            // Watchdog에 종료 신호 전송
            await _watchdog.SendControlCommandAsync("EXIT");
            await _watchdog.StopAsync();
            _watchdog.Dispose();
            _watchdog = null;
        }
        catch (Exception ex)
        {
            RaiseError($"[Watchdog Error] Watchdog 종료 오류: {ex.Message}");
        }
    }
}

// Watchdog 일시 중지
public async Task PauseWatchdogAsync()
{
    try
    {
        if (_watchdog != null)
        {
            RaiseLogMessage("[Watchdog] Watchdog 일시 중지 시도 중...");

            // 명령 전송 시도
            bool commandSent = false;
            for (int retry = 0; retry < 3; retry++)
            {
                try
                {
                    await _watchdog.SendControlCommandAsync("PAUSE");
                    commandSent = true;
                    break;
                }
                catch (Exception ex)
                {
                    RaiseError($"[Watchdog Error] Watchdog 통신 시도 {retry + 1} 실패: {ex.Message}");
                    if (retry < 2) await Task.Delay(500); // 재시도 전 대기
                }
            }

            if (commandSent)
            {
                RaiseLogMessage("[Watchdog] Watchdog 감시가 일시 중지되었습니다.");
            }
            else
            {
                RaiseError("[Watchdog Error] Watchdog와의 통신이 실패했습니다.");
            }
        }
        else
        {
            RaiseLogMessage("[Watchdog] Watchdog가 초기화되지 않았습니다.");
        }
    }
    catch (Exception ex)
    {
        RaiseError($"[Watchdog Error] Watchdog 일시 중지 중 예외 발생: {ex.Message}");
        throw; // 상위로 예외 전파
    }
}

// Watchdog 재개
public async Task ResumeWatchdogAsync()
{
    if (_watchdog != null)
    {
        await _watchdog.SendControlCommandAsync("RESUME");
        RaiseLogMessage("[Watchdog] Watchdog 감시가 재개되었습니다.");
    }
}

public async void CloseWatchdogAsync()
{
    if (_watchdog != null)
    {
        await _watchdog.StopAsync();
        _watchdog.Dispose();
    }

    if (true == _bIsCloseWithWatchdog)  //프로그램 종료시 Watchdog 프로그램도 같이 종료하기
        await StopWatchdogAsync();   // Watchdog 종료
}

 

 

 

B 프로그램

//B 프로그램

private ProcessWatchdog _watchdog = null;
private string _hostProcessPath;


InitializeWatchdogAsync();


public async Task InitializeWatchdogAsync()
{
	_hostProcessPath = @"pc_a.exe";

    try
    {
        _watchdog = new ProcessWatchdog(
            processName: "pc_a",  // 호스트 프로세스 이름
            processPath: _hostProcessPath,
            watchdogId: "WatchdogId",
            partnerId: "HostId",
            checkInterval: 2000,
            timeoutThreshold: 6000
        );

        // Watchdog 이벤트 핸들러 등록
        _watchdog.OnLogMessage += async (s, e) => await UpdateLogAsync($"[Watchdog] {e.Message}");
        _watchdog.OnError += async (s, e) => await UpdateLogAsync($"[Watchdog-Error] {e.Message}");

        // IPC 명령 수신 이벤트 핸들러 - 호스트 프로세스로부터 명령 수신
        _watchdog.OnControlCommand += async (s, command) =>
        {
            await UpdateLogAsync($"IPC 명령 수신 : {command}");

            switch (command.ToUpper())
            {
                case "EXIT":
                    _isClosing = true;
                    await CloseWatchdogAsync();
                    Application.Exit();
                    break;
                case "SHOW":
                    ShowWindow();
                    break;
                case "HIDE":
                    HideWindow();
                    break;
            }
        };

        await _watchdog.StartAsync();   //Watchdog 서비스를 시작

        await UpdateLogAsync("Watchdog 서비스가 시작되었습니다.");
    }
    catch (Exception ex)
    {
        await UpdateLogAsync($"Watchdog 초기화 오류: {ex.Message}");
        MessageBox.Show($"Watchdog 초기화 오류: {ex.Message}", "오류",
                      MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

private async Task CloseWatchdogAsync()
{
    if (_watchdog != null)
    {
        await _watchdog.StopAsync();
        _watchdog.Dispose();
        _watchdog = null;
    }
}
반응형

'C#' 카테고리의 다른 글

[visual studio] 글꼴 설정 변경 방법  (2) 2025.06.09
Moonlight, GeForce Experience 페어링, 연결 및 해제  (1) 2025.01.22