소스를 분석해 보셨나요? ^^ 조금 복잡하지요? 많이 복잡한가요? ^^

  우선 netinc.h 부터 말씀 드리겠습니다. netinc.h 는 통신 라이브러리에서 공통적으로 사용하는 상수와 필요 버퍼 및 버퍼의 크기를 정의해 놓았습니다.

이전 동영상에도 말씀을 드렸지만 소개해 드린 통신 라이브러리는 rs232c 만을 위한 통신 라이브러리가 아닙니다. 앞으로 계속 소개해 드릴 TCP/IP, UDP/IP, UDS에 대한 통신 라이브러리도 계속 추가 되기 때문에 시리얼 통신과는 관계 없는 내용이 보일 수 있습니다. 이점 참고해 주세요....^^

 

  netinc.h 를 보시면 아래와 같이 통신 중에 발생할 수 있는 에러코드가 선언되어 있습니다.

#define NERR_NONE           0                                         // 함수 실행에 에러가 없었음
#define NERR_NO_MEMORY     -1                                         // 객체 생성에 실패
#define NERR_NOT_OPENED    -2                                         // 소켓 open 에 실패
#define NERR_BIND_FAILURE  -3                                         // 소켓 bind 에 실패
#define NERR_NO_POLL       -4                                         // 폴에 추가할 빈 자리가 없음
#define NERR_NOT_CONNECT   -5                                         // 상대 호스트와 연결에 실패
#define NERR_NO_DEVICE     -6                                         // 장치명이 없음

  그리고 통신 객체를 몇 개까지 관리할 것인지에 대한 크기가 정의되어 있습니다.

#define POLL_MAX_COUNT      50                                        // poll array 의 크기, 즉 동시에 열릴 수 잇는 소켓 최대 개수

poll manager

  여기서 poll manager에 대한 말씀을 드리고 가겠습니다. poll manager는 poll() 함수를 이용하여 통신 객체 각각에 대한 이벤트를 확인하고 발생된 이벤트가 있다면 해당 통신 객체의 함수를 호출해 주는 manager 입니다. 동영상과 아래에 소개된 글을 보시면 아시겠습니다만, 본 통신 라이브러리는 각 통신 객체가 직접 자료가 수신되었는지 에러가 발생했는지 직접 알아 보고 행동하는 것이 아니라 poll manager 에 객체를 등록해 놓으면 poll manger 가 대신 확인하며, 대신에 통신 객체는 어떻게 행동할지만 정해 놓으면 됩니다.

  pollmamager.h 를 보십시오. 아래와 같이 struct 구문이 있습니다.

typedef struct net_obj_t_ net_obj_t;
struct net_obj_t_
{
  int   poll_ndx;
  int   fd;
  int   type;									    // tcp/udp/uds/serial/
  int   tag;
  void  (*on_poll_in )( net_obj_t *);
  void  (*on_poll_out)( net_obj_t *);
  void  (*on_poll_err)( net_obj_t *);
  void  (*on_poll_hup)( net_obj_t *);
};

  그리고 rs232.h 를 보시면 역시 아래의 struct 가 정의되어 있습니다.

typedef struct rs232_t_ rs232_t;
struct rs232_t_
{
  int   poll_ndx;
  int   fd;
  int   type;                                     // tcp/udp/uds/serial/
  int   tag;
  void  (*on_poll_in )( rs232_t *);
  void  (*on_poll_out)( rs232_t *);
  void  (*on_poll_err)( rs232_t *);
  void  (*on_poll_hup)( rs232_t *);

  char  devname[20];                                                  // 장치명을 담을 버퍼
  int   baud;  
  int   databit;
  int   stopbit;
  int   parity;
                    
  void  (*on_read    )( rs232_t *);
  void  (*on_writable)( rs232_t *);
  void  (*on_error   )( rs232_t *, int);
};

  rs232_t_의 색깔로 표시한 부분인 pollmanager.h 의 net_obj_t_ 의 것과 똑 같죠. 이렇게 rs232를 통신하려는 객체는 pollmanager.h 의 net_obj_t_ 를 참고하여 뒷 부분만 자신이 필요한 것을 첨부해서 struct 를 구성하고 이것을 생성함으로써 객체를 생성합니다.

  생성된 객체는 pll manger가 원하는 poll_ndx 부터 가상 함수까지 모두 가지고 있기 때문에 poll manager 가 각 통신 객체의 변수를 확인하거나 가상 함수를 호출할 수 있게 됩니다.

  pollmanager.c 에서 poll_loop() 함수를 보겠습니다.

int poll_loop( int _time_out)
{
  int     poll_state;
  int     ndx;

  poll_state = poll( (struct pollfd *)&poll_array, POLL_MAX_COUNT, _time_out);
  if ( 0 > poll_state)
  {                                                                   // 치명적인 에러가 발생했다.
    return POLL_BIG_ERR;
  }
  if ( 0 == poll_state)                                               // 타임아웃에 걸렸으므로
  {                                                                   // 다시 상태를 확인한다.
    return POLL_TIME_OUT;
  }
  for( ndx = 0; ndx < POLL_MAX_COUNT; ndx++ )      
  {
    if( poll_array[ndx].revents & POLLIN )                            // 상대로부터 자료가 수신되었음
    {
      if ( NULL != poll_objs[ndx])
      {
        poll_objs[ndx]->on_poll_in( poll_objs[ndx]);
      }
    }
    if( poll_array[ndx].revents & POLLOUT )                           // 쓰기 상태가 block 이 아님
    {
      if ( NULL != poll_objs[ndx])
      {
        poll_objs[ndx]->on_poll_out( poll_objs[ndx]);
      }
    }
    if( poll_array[ndx].revents & POLLERR )                           // 에러가 발생
    {
      if ( NULL != poll_objs[ndx])
      {
        poll_objs[ndx]->on_poll_err( poll_objs[ndx]);
      }
    }
    if( poll_array[ndx].revents & POLLHUP )                           // 라인이 끝어 졌다면
    {
      if ( NULL != poll_objs[ndx])
      {
        poll_objs[ndx]->on_poll_hup( poll_objs[ndx]);
      }
    }
  }
  return  POLL_EVENTED;
}

  poll_loop()는 계속 호출되어지는 함수로 한번 호출 될 때 마다 인수로 받은 _time_out 시간 만큼 대기 하고 있으면서 등록된 통신 객체별로 이벤트 상태를 확인합니다. 객체에 따라 자료가 수신되거나 에러가 발생하면 위의 소스에서 빨강색의 행이 실행됩니다.

poll_objs[ndx]->on_poll_in( poll_objs[ndx]);
poll_objs[ndx]->on_poll_out( poll_objs[ndx]);
poll_objs[ndx]->on_poll_err( poll_objs[ndx]);
poll_objs[ndx]->on_poll_hup( poll_objs[ndx]);

  이렇게 각 객체별로 이벤트가 발생하면 각 통신 객체에 미리 준비된 함수를 호출함으로써, 마치 이벤트 처리 방식처럼 프로그램을 구성하실 수 있습니다.

  이제 rs232.c에서 어떻게 on_poll_in 과 on_poll_out 과 같은 함수를 구성했는지 보시겠습니다.

static void on_rs232_poll_in( rs232_t *sender)
{
  int   sz_read;

                                  // 데이터 핸들러가 없을 때에도
                                  // poll 검색이 계속 발생되는 것을 막기 위해
                                  // 수신 버퍼를 비운다.

  if ( NULL == sender->on_read)
  {
                                  // 읽어 들인 데이터의 크기가 0 이하라면
                                  // 소켓에 에러가 발생한 경우 이므로
                                  // on_disconnected 를 발생하고 소켓을 close 한다.

    sz_read = read( sender->fd, __sock_buf, __MAX_BUF_SIZE);
    if ( 0 >= sz_read)
    {
      if ( NULL != sender->on_error)
      {
        on_rs232_poll_err( sender);  
      }
    }
  }
  else
  {
    sender->on_read( sender);
  }
}

  여기서 보시면 sender가 바로 통신객체입니다. 객체의 on_read() 함수를 호출하고 있습니다. 그러므로 통신객체는 on_read() 함수만 미리 준비해 놓으면 자료가 수신이 되면 알아서 사용자의 on_read() 함수가 호출되게 됩니다.

  잉? 뭘 이렇게 복잡하게 해놨어? 하실지 모르겠습니다. on_rs232c_poll_in 만 가지고도 사용할 수 있는데, 왜 굳이 on_read 로 다시 구성했을까 하는 것이죠. 맞습니다. on_rs232c_poll_in 만 가지고도 하실 수 있고, 아예 on_rs232c_poll_in에 대한 on_poll_in을 다른 함수의 포인터로 변경하셔도 됩니다.

  이렇게 구성한 이유는 사용자가 정의하고 싶지 않은 이벤트가 있다면 이 이벤트를 작성하지 않아도 기본적이 처리를 하기 위함입니다. 또한 각 객체의 on_poll_in을 호출할 수 있어서 기본적인 기능을 외부에서 사용할 수 있기 때문입니다.

Event driven 방식

  자, 이제 sample.c 를 다시 한번 보시죠. rs232 통신을 하면서도 많은 코드가 필요가 없죠. 우선 자료를 수신할 때의 처리를 위해 void on_read( rs232_t *sender)를 준비했고, 에러 이벤트를 위해 void on_error( rs232_t *sender, int reopen_ok) 를 준비했습니다. 그리고 그 함수 안에서 어떻게 처리할 지를 코드해 넣었습니다.

  그리고 프로그램이 실행될 때, rs232 통신을 위한 객체를 생성하고,

rs232  = rs232_open( "/dev/ttyS0", 115200, 8, 'n', 1);

  각 이벤트 처리 함수를 함수 포인터를 이용하여 등록해 놓습니다.

rs232->on_read  = on_read;
rs232->on_error = on_error;

  이제 poll manager 의 loop 를 호출하면, 호출될 때 마다 pollmanager 가 알아서 이벤트 발생 여부를 확인하고 발생하면 알아서 on_read와 on_err을 호출하게 됩니다. 마치 Event Driven 방식처럼 말이죠.....^^