비동기 OVERLAPPED 읽기 수행하기

 

일단 비동기 read부터 알아보자. 역시나 백문이 불여일견이니 코드부터 보자

 

DWORD CSerialz::ReadData(BYTE *pBuff, DWORD nToRead)
{
 DWORD dwRead;
 DWORD dwError;

 //COMSTAT은 현재 읽기, 쓰기 버퍼등 통신에 관련된 상태를 담는 구조체
 DWORD dwErrorFlags;
 COMSTAT comstat;

 

//장치를 사용 할 수 있도록 오류플래그를 받아오고 오류 상태를 해제한다.

//그리고 장치 상태를 얻어온다.
ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
 
 // 읽기버퍼에 들어와있지만 아직 읽지 못하고 남아있는 바이트 수
 dwRead = comstat.cbInQue;

 

//읽기 버퍼에 아직 읽지 못하고 남아있는 바이트가 남아 있는 경우
 if(dwRead > 0)
 {
    //읽기 버퍼를 nToRead만큼 읽어 들여온다. 그리고 읽기 성공한 바이트 수를 

    //dwRead에 다시 저장 비동기 함수라 바로 FALSE 반환
    if(!ReadFile(m_hIDComDev, pBuff, nToRead, &dwRead, &m_OverlappedRead))
   {
       //정상적으로 읽기 작업이 작업 큐에 저장
       if(GetLastError() == ERROR_IO_PENDING)
      { 
          // timeouts에 정해준 시간만큼 기다려준다.

          //COMMTIMEOUTS에 읽기는 기다리지 않도록 설정한 상태
          while (! GetOverlappedResult(m_hIDComDev,

                                                    &m_OverlappedRead,

                                                    &dwRead,

                                                    TRUE))
          {
               //읽을 때까지 기다렸는데 미리 설정한 타임아웃 이내에 읽지 못한 경우 
               dwError = GetLastError();
               if (dwError != ERROR_IO_INCOMPLETE)
               {

                   //장치의 에러 상태를 해제한다.
                   ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
                   break;

                }
           }
       }

       //작업큐에 읽기 작업이 정상적으로 저장되지 못한 경우
       else
       {
            dwRead = 0;

             //에러상태를 해제한다.
            ClearCommError(m_hIDComDev, &dwErrorFlags, &comstat);
        }
     }
 }

 

 //실제로 읽은 바이트 수 반환
 return(dwRead); 

 

뭐 딱히 별다른 설명이 필요하지 않다. 읽는 것 자체는 Overlapped write와 크게 다르지 않다. 다르다고 할 만한게 있다면 ClearCommError의 사용 정도가 있겠다. ClearCommError 함수는 파일디스크립터 (핸들) 이 가리키는 IO Device를 사용 할 수 있도록 에러 상황을 받아오고서는 에러 상태를 해제한다. 그리고 IO Device관련된 정보를 받아오는 기능을 한다.

 

이제 우리는 읽기 버퍼로부터 바이트를 읽어 올 수 있게 되었다. 그렇다면 이제 남아있는 문제는 "어떻게 읽기 버퍼로 읽을 수 있는 바이트가 저장되는 것을 알아 차릴 수 있는 가?"이다. 방법은 쓰레드를 이용해 읽기 버퍼에 데이터가 들어올 때 발생하는 이벤트를 감시하고 있는 것인데 쓰레드로 돌릴 함수는 아래와 같다.

 

//COM 포트에서 이벤트가 일어나는지 감시하는 스레드 함수 DWORD CSerialz::THWatchEvPort(CSerialz *pComm)
{
   DWORD  dwEvent;
   OVERLAPPED os;
   BOOL  bOk = TRUE;  
   BYTE  buff[4096]; 
   DWORD  dwBufferCounter;   

 

   // Overap Structure 설정
   memset(&os, 0, sizeof(OVERLAPPED));
   if(!(os.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL))) bOk = FALSE;

 

   // m_hIDComDev COM 포트의 읽기 버퍼에 데이터가 들어왔을 때 발생하는

   //이벤트및 몇가지 에러 상황을 감지하도록 설정
   if(! SetCommMask(pComm->m_hIDComDev, 

                             EV_RXCHAR  |  EV_BREAK  |  EV_ERR )) bOk = FALSE;

 

   // OVERLAPPED 구조체 설정및 COM포트의 감지 이벤트 설정 중에 문제가

   //발생했으면 return FALSE
   if(!bOk) { 

      AfxMessageBox(_T("Error while Receive Waiting") + pComm->szPort); 

      return FALSE; 

   }

 

   // port가 열려 있는 동안.....포트가 PortClose에 의해 닫히면 m_bPortOnOff

   // 가 FALSE가 되어 종료.
   while(pComm->m_bPortOnOff)  
   {         
      dwEvent = 0;
    
       //이미 이벤트가 발생해 있는 상태라면 TRUE 반환, 이벤트 미발생 상태로

       //펜딩되면 FALSE 반환. OVERLAPPED의 이벤트는 함수 호출시 자동 RESET   
       BOOL result = WaitCommEvent(pComm->m_hIDComDev, &dwEvent, &os);  
  
       //WaitCommEvent함수 호출시 이벤트 발생된 상태로 바로 이벤트 감지
       if(result)
       {
          //DoNothing
        }
        else
       {
           //WaitCommEvent가 PENDING된 경우
           if(GetLastError() == ERROR_IO_PENDING)
          {

              //이벤트가 발생하면 다음단계로 진행
              WaitForSingleObject(os.hEvent,INFINITE);
           }  

          //에러로 인한 이벤트 발생시 스레드 종료
          else
          {
                break;
           }
      }     

      

      //발생 이벤트가 읽기 버퍼에 데이터 수신으로 인한 이벤트인 경우
      if((dwEvent & EV_RXCHAR) == EV_RXCHAR)
      {   // 포트에서 읽을 수 있는 만큼 읽는다.
          do

          {

                //읽기 버퍼에서 4096 (전체 버퍼 싸이즈) 만큼 읽어 들여와서 읽기

                //성공한 바이트 수를 dwBufferConter에 저장한다.
                dwBufferCounter = pComm->ReadData(buff, 4096);    // 데이터 읽기. 
                pComm->FrmTHRcvData(buff, (BYTE)dwBufferCounter);  //data 사용   
           }while (dwBufferCounter); // 읽을 수 있는 data가 없을 때까지 읽어온다.   
       }
       else
      {
          //에러 상황으로 인해 이벤트가 발생한 경우는 그냥 이 스레드를 종료시킴
          break;
       }
    }

 //OVERLAPPED 구조체의 이벤트 핸들을 닫고 스레드 포인터를 NULL로 한다.
 CloseHandle(os.hEvent);
 pComm->m_ThrdEvPort = NULL;

 return(TRUE);
}

 

주석만으로 전체 코드 구성을 이해 하는데 충분하다고 생각한다. 위의 함수를 CreateThread함수나 AfxBeginThread를 이용하여 돌리면, 읽기 버퍼에 데이터가 들어오는 경우 읽기 버퍼에서 더이상 읽을 버퍼가 없을 때까지 읽어들여 온다.

 

이상 RS232를 이용한 시리얼 통신 코드는 링크를 통해 볼 수 있다.(비공개)

 

Posted by 굿쟌
,