Modbus TCP 제어 ( 패킷 과수신 대비 )

2024. 3. 6. 21:38개발/C#

Modbus TCP 통신을 사용해서

실제 장비를 제어하는 로직을 사용중이었다.

 

하나의 프로그램에 프로세스가 총 3개 동작하고

1.MQTT 패킷을 수신

2.제어부 프로세스까지 전달(Socket)

3.실 제어 및 상태값 검증

 

위와 같이 제어를 하도록 개발했다.

하지만 곰곰히 생각해보니

 

내가 개발한 코드는, 단순히 '제어'만을 목적을 둔 단순한 코드고

예외상황을 생각하지 못한 안좋은 코드같았다.


수정 전

 

간단히 시퀀스 다이어그램을 제작했다.

Process A 에서, MQTT 브로커로부터 

제어 메시지를 수신받는다.

 

수신받은 메시지를, Process A & B 를 관리하는

Main Process에게 전달.

Main Process 는 실 제어부 동작을 하는

Process B 로 패킷을 전달한다.

 

Process B 는 Modbus TCP 통신을 통해

실제 장비를 Write & Read 한다.

 

Process B는 제어한 뒤,

최초 전달받은 제어 패킷의 데이터 ( on/off ) 값과

제어한 뒤 상태값 ( on/off ) 를 비교하여

성공여부를 판단한다.


문제점

얼핏 보면 문제가 없어보인다.

제어 패킷 프로토콜 검증도 하고,

제어한 뒤 상태값도 검증하니까 괜찮아 보이는데

 

제어 패킷을 '1회' 수신받았을 때는 매우 정상적으로 동작하는 코드다.

 

요즘 개발하면서, 회사 규모가 크진 않아도

내가 개발하는 서비스는 사용자가 많다고 상상하고 개발하려고 노력중이다.

그러다 보니 문제점을 떠올릴 수 있었던 것 같다.

 

만약, 누군가 시스템을 해킹해서

MQTT Broker가 노출되었고,

1초동안 제어 패킷이 10000회 수신된다면 어떻게할래? 하고 스스로 물었을 때

 

'고장내고 욕먹을래' 라는 대답밖에 떠오르지 않아서

당장 코드를 수정하기로 했다.

 

10000회는 오바일수도 있지만, 당장 1초에 5개? 만 들어와도

Process B가 제어중일 때, Process A 가 제어메시지를 또 수신받아서

이미 Write 중인 Process B에게 또 Write명령을 내리면

통신 오류가 발생할 가능성이 높다.


문제 단계별 해결법

여러가지 문제들이 떠올랐고,

각 문제별로 어떻게 해결할지 상상해봤다.

 

1.메시지 과수신

   i)  메시지 차단 ( 일정횟수 이상 수신 시 )

일정횟수 이상을 차단하는건 간단하고 좋지만,

중요한 제어 메시지를 휙 하고 버려버릴 수 있는 위험한 로직이라고 생각했다.

 

   ii) 메시지 수신 보안 강화 ( 3 way hand-shake 처럼, 제어 메시지에 대한 응답값 송/수신 후 실 제어 ) 

확실하고, 안전한 제어가 가능할 것 같지만, 송/수신 사이에 네트워크 문제가 발생했을 때

과거 패킷 관리, 재연결 시 제어 등의 문제가 까다로웠다.

 

2.비순차적인 제어 로직

   i)  flag 활용 ( flag = false 일때만 제어 시도 )

간단해서 바로 떠오른 방법인데, flag 세우는건 좋지만

제어 도중에 또다른 제어 명령이 들어왔을 때, 나중에 들어온 명령을 관리하는게 불가피했다.

flag 세우는것 만으로는 제어 로직을 순차적으로 관리하기 힘들어보였다. 

 

   ii) 자료구조 활용 ( Queue를 활용한 패킷 관리 )

이게 제일 좋아보였다. 메시지 수신 시, Queue에 패킷을 Enqueue하고

또 다른 프로세스 or 스레드에서 Queue 를 Dequeue 및 실 제어를 하면

깔끔하고, 안전하게 제어를 할 수 있을거라 생각했다.

안그래도 Queue가 선입선출 방식이니, 제어 명령도 순차적으로 진행할 수 있을 것 같았다.


수정 후

로직을 위와같이 수정했다.

(1) 에서, 초당 10회만 수신하도록 처리했다.

MQTT 수신 이벤트 발생 시, count를 증가시켰고

ctrlRcvCount는, 타이머에 의해 1초마다 초기화된다.

 

즉, 1초동안 10회이상 수신되면

10회 이상값부터는 제어 명령이 아니라 판단하고 무시한다.

 

10회 미만일때만 OnRecived 이벤트를 Invoke() 한다.

 

(2) 스레드 소스

Process B에 (2)스레드를 추가했다.

해당 스레드는, Queue 크기를 3초마다 확인하며

Queue 크기가 0 이상일 때 ( 제어 명령이 있을 때 )

Dequeue 후 제어 패킷에 맞게 실 제어부를 동작시킨다.

 

(3) 제어 메시지 수신

수정 전에는, (3) 처럼 Queue저장 과정이 없이,

제어 메시지 수신 하자마자 바로 제어를 했다.

 

수정 후에는, 제어 메시지 수신 시

실 제어 하지않고, Queue에 패킷을 저장한다.

lock 처리를 해서, 실 제어부 동작과 충돌나지 않게 분리시켰다.


또한, 제어 log 파일을 따로 관리해서

기존 log 파일과 별개로 영구보관했다.

 

아무래도 실제 산업현장의 장비를 제어하다보니

문제가 생길 가능성이 있기 때문에

기록용? 보험? 느낌으로 영구저장 하려한다.

 

테스트 결과는 성공~

 

수정하고 나서 더 안전하고 효율적으로 제어가 가능해졌다.

'개발 > C#' 카테고리의 다른 글

C# 선그래프  (1) 2024.04.02
Encoding  (1) 2024.02.14
C# - class & structure  (1) 2024.02.10
소프트웨어 설계 수정  (0) 2024.01.08
모니터링 자동화 - API Parsing  (1) 2024.01.01