리눅스 C언어를 사용하시는 분은 아마도 poll() 함수를 잘 아실 것입니다. poll() 함수를 간단히 설명 드리면, 확인하고자 하는 이벤트의 발생 여부를 알기 위해 일정 CPU 시간을 시스템에 돌려 주는 것입니다. poll() 함수를 호출하는 것은 sleep() 함수를 호출하는 것처럼 일정 시간만큼 sleep 상태가 되지요.


단, sleep()과 다른 점은 미리 등록한 이벤트가 발생하면 바로 복귀한다는 점입니다.


통신에서 주의할 점은 상대 쪽에서 전송한 데이터를 적절한 타이밍에서 수신하는 것입니다. 매우 당연한 얘기이지만, 문제는 상대방이 언제 데이터를 전송하는 지, 얼마만한 크기의 데이터를 보내는지를 알 수 없다는 것입니다. 데이터 크기야 프로토콜로 확인할 수 있어도 시간은 알기 어렵죠. 그렇다고 어플리케이션에서 수신 버퍼를 계속 확인하기 위해 계속 루프를 돌아야 한다면, 아까운 CPU 타임을 소모하게 됩니다. 차라리 이 시간을 다른 어플리케이션이나 시스템에서 사용할 수 있게 한다면 더욱 유연하고 가벼운 프로그램으로 만들 수 있습니다.


예를 들어 rs232c 시리얼 통신을 하면서 상대가 언제 보내 줄지 모르는 데이터를 수신하기 위해서 마냥 시리얼 포트를 읽어서 확인한다고 하면 그만큼 어플리케이션에 할당된 CPU 시간을 모두 사용하게 됩니다. 그러나 poll() 함수를 통해 rs232c의 수신 상태를 등록하고 일정 시간 지정하여 poll()를 호출하면 poll() 함수는 그 일정 시간의 CPU 타임을 다른 곳에서 사용하게 하고, 만일 수신 데이터가 있다면 바로 복귀합니다.


이런 이유로 같은 while() 문을 사용해도 poll() 함수를 사용하면 시스템에 부담을 주지 않는 가벼운 프로그램을 만들 수 있는 것입니다.

while(true){
   if ( 0 < read( fd_rs332)){
   }
}

이렇게 처리하기 보다는

// rd_rs232의 입력 이벤트 등록
while( true){
     poll( 100);
}

이렇게 poll() 함수를 사용하는 것이 좋습니다. 여기서 poll()함수는 기대하는 이벤트가 발생하면 바로 복귀하고 발생하지 않으면 지정한 시간만큼 대기했다가 복귀한다고 말씀 드렸습니다. 즉, 이벤트가 발생하든, 발생하지 않든 poll()함수는 복귀합니다. 그러므로 통신 외에 계속 처리해야할 일이 있다면 poll() 함수 호출 전이나 호출 후에 실행하면 되겠습니다.


당연한 얘기이지만, 한 가지 주의할 것이 있습니다. 상대방이 전송하는 데이터가 한 두 바이트가 아니라면 poll() 함수는 계속해서 복귀할 것입니다. 즉, 데이터를 부분 부분 수신하면 반복 작업을 계속 실행하게 되는 경우가 생깁니다.


예를 들어 LED를 깜빡이는 작업을 계속 하겠습니다. 그리고 그 함수 이름을 blink_led()라고 하겠습니다. 만일 LED를 1초마다 깜빡이게 하고 싶다면 아래와 같이 처리하면 어떻게 될까요?

while( true){
     poll( 1000);
     blink_led();
}

안 되는 것은 아니지만, 이렇게 처리해서는 안 되겠습니다. 상대 쪽에서 데이터를 전송하지 않는다면, 즉 이벤트가 발생하지 않는다면 poll() 함수가 1000msec, 즉 1초를 대기하고 blink_led()함수를 호출하겠지만, 수신해야 할 데이터가 있다면 poll() 함수는 바로 복귀하기 때문에, 1초 대기 없이 계속해서 LED가 빠르게 점멸할 것입니다.


그렇다면? 시간을 비교하는 루틴을 넣어야겠습니다.

     long tmCurrent = 0L;
     long tmBefore = 0L;
     swSerial = WAIT_STX;

     while (true) {
          poll.pollDoLoop(100);

          tmCurrent = System.currentTimeMillis();
          if ((tmCurrent - tmBefore) > 1000) {
               tmBefore = tmCurrent;
               blink_led();
          }
     }

while() 문제 추가한 시간 비교 루틴은 일정 시간마다 기능을 실행하므로 타이머라고 할 수 있습니다. 즉, 타이머를 만들기 위해 쓰레드를 이용하거나 타이머 클래스를 사용할 필요 없습니다. 시간만 비교하는 if 절을 추가하면 여러 개의 타이머를 생성할 수 있습니다.

     long tmCurrent = 0L;
     long tm1sec = 0L;
     long tm5sec = 0L;
     swSerial = WAIT_STX;

     while (true) {
          poll.pollDoLoop(100);

          tmCurrent = System.currentTimeMillis();
          if ((tmCurrent - tm1sec) > 1000) {
               tm1sec = tmCurrent;
               on_every_1sec();
          }
          if ((tmCurrent - tm5sec) > 5000) {
               tm5sec = tmCurrent;
               on_every_5sec();
          }
     }

통신 프로그램에서는 타이머를 잘 활용해야 합니다. 그렇다면 과연 통신 프로그램에서 타이머를 어디에 사용할까요? 우선 아래와 같은 경우를 위해 통신 프로그램에서 타이머를 이용할 수 있습니다.


  1. 수신 지연에 따른 루틴 초기화
  2. 상대 응답 대기 시간 확인

위의 2가지 외에도 타이머는 다양하게 사용할 수 있습니다만, 대표적인 경우라서 설명을 드립니다. 우선 1번 수신 지연에 따른 루틴 초기화는 시리얼 통신에서 꼭 필요한 기능입니다. 시리얼 통신은 전송하는 쪽의 데이터를 시스템이 일단 모두 받은 후에 애플리케이션으로 보내는 것이 아니라 한 두 바이트라도 수신되면 수신되는 대로 계속해서 애플리케이션으로 전달합니다. 그러므로 프로토콜에 맞추어 애플리케이션이 실제 데이터를 가려 내어야 합니다.


시리얼 통신에서는 보통 데이터를 전송하기 전에 전송 시작을 알리는 STX 코드를 먼저 보내 주고 데이터를 모두 전송 후에는 전송 완료를 알려 주는 ETX 코드를 보내 줍니다. 수신하는 쪽은 STX 코드를 받으면 버퍼를 준비하고 있다가 ETX 코드를 받기 전까지 계속 수신합니다. 그리고 ETX를 코드를 받으면 데이터 수신을 완료합니다.


그런데 송신하는 쪽에서 STX를 보내고 이어서 데이터를 전송하는 중에 프로그램이 종료되었다면 어떻게해야할까요? 또는 송신하는 쪽에서 분명히 ETX를 보냈지만, 수신하는 도중에 깨져서 수신 측에서 알지 못한다면 어떻게 될까요?


두 경우 모두 예외 상황을 생각하지 않았다면 ETX를 받기 위해 마냥 대기할 것입니다. 그러므로 일정 시간 기다리다가 데이터 송신이 중지되었거나 받아야할 데이터가 오지 않는다면 송신측에 다시 요청해야 하는데, 이럴 때 타이머가 필요합니다. 두 번째 상대 응답 대기 시간에 타이머를 확인하는 이유는 상대의 시스템 상태를 알 수 없기 때문입니다.


데이터를 요청했는데 일정 시간 전송해 오는 데이터가 없다면 몇 차례 다시 요청하고 역시 응답이 없다면 상대 시스템이 꺼졌거나 프로그램 오류가 발생하여 정상적으로 운영할 수 없다고 판단할 수 있습니다. 이와 같은 판단으로 모니터링 시스템에 네트워크 오류를 표시하거나 관리자에게 문제를 해결할 수 있도록 알림을 전송해야 합니다.


어떻습니까? 통신에서 타이머는 매우 중요하지요? 다음 시간에 통신 중 타이머 활용에 대해 자세히 올리겠습니다.