이번에는 쓰레드가 아닌 타이머를 이용해서 UDS통신을 하는 방법에 대해 알아 보도록 하게습니다. 그러나 이 방법은 어디까지나 저 개인적으로 생각해 낸 것으로 퍼포먼스나 프그래밍의 편리함이 떨어 질 수 있습니다. 즉, 더 편리하고, 더 효율적인 방법이 있을 것이라 생각됩니다.

그럼에도 이 방법을 올리는 이유는 제가 정말 쓰레드가 싫기 때문입니다. QT가 분명 thread-safe를 제공하지만 그렇다고 쓰레드의 단점이 없어지는 것은 아닙니다.

또한 이렇게 타이머를 이용하는 이유는 정말 QSocket처럼 시그널과 슬롯을 만드는 방법을 모르기 때문입니다. 이것이 어려운 점은 이번 시간에도 언급드리겠습니다만 소켓 통신에서는 시리얼과는 달리, 수신할 자료가 없는데 읽기를 하면 상대방이 송신할 때까지는 block되기 때문입니다.

자 그럼 이번 시간에는 타이머를 두고, 이전 프로그램처럼 수신 받은 자료를 다시 반송하는 프로그램을 작성해 보도록 하겠습니다.

역시 프로젝트파일부터 메인폼까지 파일을 생성하겠습니다.

프로그램 코딩 시작

이번에 만들 UDS 프로그램은 첫번째 예제 답게 상대편으로부터 전송되어온 문자열을 반송하는 에코 프로그램입니다. 수신한 문자열은 QLabel을 통해 출력하겠습니다.

우선 프로젝트 파일부터 메인폼까지 먼저 만들어야 겠지요. ^^

  1. 프로제트 파일 생성, "C++ Project"를 선택합니다.


  2. 적당한 프로젝트 이름을 입력합니다.


  3. 프로젝트 파일을 저장합니다.


  4. 폼 이름을 지정합니다. 이 이름이 클래스 이름이 되므로 적당한 이름을 지정해 줍니다. 여기서는 frmMain으로 입력하겠습니다.


  5. 필요한 위젯을 배치합니다.


  6. labCounter는 타이머 작동을 확인하기 위해 준비했으며, 타이머가 작동할 때 마다, 숫자가 하나씩 증가할 것입니다.

  7. labReceive 라벨은 수신된 자료를 출력하겠습니다.

  8. 폼 생성 후에 실행할 init()함수와 타이머의 슬롯함수를 등록합니다.


  9. 우선 전체 소스를 보시겠습니다. 타이머(tmrCenter)나 소켓 디스크립터(sock), 카운터(nCounter)는 모두 frmMain에 포함되어야할 멤버 요소입니다만, 예제 소스를 간편하게 소개해 드리기 위해서 모두 로컬 변수로 선언했습니다.

    위 그림의 ObjectExplorer에서 init() 함수나 onCenterTimer() 슬롯을 클릭하면 에디터가 출력되고, 아래의 소스를 작성하실 수 있습니다. 파일 이름은 frmMain.ui.h 입니다.

    자세한 설명은 프로그램의 소스에 주석으로 넣겠습니다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/un.h>
#include <qapplication.h>
#include <qtimer.h>
#include <qmessagebox.h>

#define  SOCK_LOCALFILE    "/tmp/uds_a"
#define  BUFF_SIZE         1024

char     buffReceive[BUFF_SIZE+5];

size_t   szAddr;
struct   sockaddr_un   addrLocal;
struct   sockaddr_un   addrGuest;

QTimer  *tmrCenter;
int      sock;
int      nCounter;

// 폼이 생성된 후에 불리워지는 함수, init()입니다.
// 타이머를 생성하고 소켓을 생성하겠습니다.

void frmMain::init()
{                                  
   // 타이머를 생성하고 카운터를 0으로 초기화 합니다.
   
   tmrCenter   = new QTimer( this);
   nCounter = 0;

   // UDS에서 사용할 파일이 존재하면 삭제합니다.

   if ( 0 == access( SOCK_LOCALFILE, F_OK))
      unlink( SOCK_LOCALFILE);

   // UDS 소켓을 생성합니다.

   sock  = socket( PF_FILE, SOCK_DGRAM, 0);
   if( -1 == sock)
   {
      QMessageBox::information( this, "UDS", "I cannot build a socket!!");
      return;
   }

   // UDS 소켓 정보를 완성하고 bind()함수를 호출하여
   // 다른 시스템에서 전송되어 오는 자료를 수신할 준비를 합니다.

   memset( &addrLocal, 0, sizeof( addrLocal));
   addrLocal.sun_family = AF_UNIX;
   strcpy( addrLocal.sun_path, SOCK_LOCALFILE);
   if( -1 == bind( sock, (struct sockaddr*)&addrLocal, sizeof( addrLocal)) )
   {
      QMessageBox::information( this, "UDS", "bind error!!");
      return;
   }

   // 타이머와 종료 버튼의 시그널에 대해 슬롯을 연결합니다.
   
   connect( tmrCenter, SIGNAL( timeout()), this, SLOT( onCenterTimer()));
   connect( btnClose, SIGNAL(  clicked()), qApp, SLOT( quit()));
   
   // 타이머를 가동합니다.
   
   tmrCenter->start(500, false);
}

// init() 함수에서는 타이머와 소켓을 준비했습니다.
// 이제 타이머 가동되면 labCounter에 숫자를 증가하여 출력하고,
// 상대방으로부터 수신된 자료가 있다면 반송하겠습니다.

void frmMain::onCenterTimer()
{
   int   szRead;

   // 카운터를 출력하고 증가합니다.

   labCounter->setNum( nCounter++);
                                   
   // 상대 시스템으로부터 전송된 자료가 있다면
   // 수신하기 위해 대기하고,
   // 수신했다면 buffReceive로 받아 옵니다.
                                   
   szAddr  = sizeof( addrGuest);
   memset( buffReceive, 0, BUFF_SIZE);
   szRead = recvfrom( sock, buffReceive, BUFF_SIZE, 0 ,
                  ( struct sockaddr*)&addrGuest, &szAddr);

   // 여기까지 실행되었다면 자료가 수신된 것입니다.
   // 화면의 라벨에 출력하고

   buffReceive[szRead]  = '\0';
   labReceive->setText( buffReceive);

   // 상대방 시스템으로 전송합니다.

   sendto( sock, buffReceive, szRead+1, 0,
            ( struct sockaddr*)&addrGuest, szAddr);
}

지금 소스 코드를 보시면 아시겠습니다만 쓰레드가 아니라 타이머이기 때문에 void frmMain::onCenterTimer() 안에 폼의 TextLabel에 직접 값을 지정해 줄 수 있습니다. 또한 필요하다면 QMessageBox::information() 과 같은 대화상자 출력 같은 것도 무리없이 사용할 수 있습니다.

프로그램 완성

main()함수를 위해 main.cpp를 생성하고 컴파일하면 모든 작업이 끝납니다.

프로젝트를 빌드하기 위해서 main.cpp를 생성합니다. File >> New메뉴를 선택합니다. main.cpp를 선택합니다.

frmMain을 선택하고 프로젝트를 빌드합니다.

]# qmake
]# make
]# ./uds   -> 프로젝트 파일 이름

프로그램 실행

프로그램이 실행된 모습입니다.

.

프로그램 테스트를 위해 .tester_user 폴더에 uds.c 파일을 준비했습니다. 이 프로그램을 컴파일을 한 수 실행해 보십시오.

]$ gcc uds.c
]$ ./a.out forum.falinux.com

이렇게 UDS로 문자열을 전송하면 frmMain 윈도우는 카운터와 함께 수신된 자료가 출력됩니다.

자, 그런데 문제가 있죠. 앞서 말씀 드린 바와 같이 소켓 함수인 recvfrom()는 상대방이 데이터를 전송하기 전까지는 block되어 버립니다. close 버튼을 클릭해도 먹히지를 않습니다. 당연히 카운터 증가도 되지 않고, 상대방이 자료를 전송해야 비로서 카운터가 증가됩니다.

이래서는 안되겠지요. 이래서 QThread를 사용해야 했습니다. 그렇다면 쓰레드를 사용하지 않고 이 문제는 어떻게 해결할 수 있을까요?

제가 생각한 방법은 POLL을 이용하는 방법입니다. 다음 시간에는 POLL을 이용하여 프로그램을 다시 구성해 보겠습니다.

POLL을 처음 접하시는 분은 아래의 글을 참고하여 주십시오.