MFC 스레드 함수

wsung·2026년 1월 19일

현재 스레드 구조 요약

UI 스레드 (메인)

  • RS232 수신 스레드(commThread)
  • UDP 수신 스레드 (UdpRecvThread)
  • UDP 폴링 스레드 (UdpPollThread)
  • 바코드 수신 스레드 (BarcodeThread)
    => 핵심 : UI스레드는 절대로 통신 블로킹을 하지 않는다. 전부 별도로 작업 스레드로 분리

RS232 수신 스레드 (CommThread)

UINT CRS232SwitchLEDMonitorDlg::CommThread(LPVOID pParam)

▶생성위치
m_pThread = AfxBeginThread(CommThread, this);

▶종료 제어 변수
BOOL m_bThreadRun;

스레드 목적

  1. rs232통신에서 주기적으로 Poll송신
  2. 일정시간 동안 응답 수신
  3. 패킷 완성 시 처리

내부 흐름 (중요)

  1. 스레드 진입
    CRS232SwitchLEDMonitorDlg pDlg = (CRS232SwitchLEDMonitorDlg)pParam;
    => UI객체 접근용

  2. 무한루프 구조
    while(pDlg -> m_bThreadRun)
    => FALSE되면 스레드 종료

  3. Poll 송신
    pDlg -> SendPollPacket();
    => pc -> 보드
    => Led 상태 유지 + 스위치 상태 요청

  4. 응답 수신 타임슬롯 (10ms)
    DWORD startTick = GetTickCount();
    while(GetTickCount () - startTick < 10)
    => RS232특성
    - 응답이 바로 안 올 수 있음
    - 무한대기 X -> 타임 아웃 필수

  5. ReadFile(1바이트씩)
    ReadFile(pDlg -> m_hCom, &rx, 1, &dwRead, NULL)

  • STX(0x02) 부터 패킷 시작
  • ETX(0x03) 까지 누적
  1. 패킷 완성
    pDlg -> DebugPrintPacket(_T("RX"), packet);
    pDlg -> ProcessPacket(packet);
  • RS232 / UDP 공용 처리
  1. 주기 유지
    Sleep(10);

=> 정리
송신: Poll 방식
수신: Byte Stream
종료: m_bThreadRun
블로킹: ReadFile + Sleep
안정성: 타임아웃 있음


UDP 수신 스레드 (UdpRecvThread)

UINT CRS232SwitchLEDMonitorDlg::UdpRecvThread(LPVOID pParam)

▶ 생성위치
m_pUdpThread = AfxBeginThread(UdpRecvThread, this);

▶ 종료 제어
BOOL m_bUdpThreadRun;

스레드목적

  • UDP로 들어오는 패킷을
    - 즉시수신
    • 패킷 단위로 처리

내부구조

  1. 파라미터 캐스팅
    CRS232SwitchLEDMonitorDlg pDlg = (CR232SwitchLEDMonitorDlg)pParam;

  2. 수신 버퍼
    Byte buf[1024];
    => UDP = 패킷단위
    => 한 번 recvfrom = 한 패킷

  3. recvfrom 블로킹
    int recvlen = recvfrom(...)
    => 완전 블로킹
    - 데이터 오기 전까지 대기
    - CPU 낭비 없음

  4. 정상 수진
    if(recvLen > 0)

  • 받은 길이 만큼 packet 생성
  • RS232와 동일처리
  1. 종료 조건
    while(pDlg-> m_bUdpThreadRun)
  • closeSocket() 호출 시
    -> recvfrom이 깨지고 스레드 종료 가능

UDP 수신 스레드 특징
수신방식 : 패킷단위
대기방식 : Blocking
타임아웃 : 없음
CPU 사용 : 매우적음
종료주의 : socket close필요

UDP 폴링 스레드(UdpPollThread)

UINT CR232SwitchLEDMonitorDlg::UdpPollThread(LPVOID pParam)

▶ 생성 위치
m_pUdpPollThread = AfxBegin(UdpPollThread, this);

▶ 종료 제어
BOOL m_bUdpPollRun;

스레드 목적

  • 보드가 먼저 보내지 않기 때문에 pc가 주기적으로 상태 요청

내부 구조
while(pDlg -> m_bUdpPollRun)
{
pDlg->sendPollPacket();
Sleep(50);
}

  • 50m 주기
  • RS232 Poll 구조 그대로 유지
  • 통신 방식만 UDP
  • poll 스레드 따로 두는 이유
  • 응답 지연방지 : 수신 스레드와 분리
  • 구조 명확 : 송신/수신 분리
  • 확장성 : TCP도 그대로 사용 가능

  • 스레드에서 다이얼로그 멤버에 접근해야함
    - 다이얼로그 클래스의 객체의 멤버 변수들 에 접근해야 스레드를 작업 할 수 있기 때문
    - 스레드 안에서 m_udpSocket, m_bUdpThreadRun, ProcessPacket() 같은 멤버들을 써야하니깐 다이얼로그 객체의 주소인 this가 꼭 필요함
  • 그래서 다이얼 로그 객체를 스레드에 연결한다
    - AfxBeginThread(UdpRecvThread, this); 이게 바로 스레드야 이 다이얼로그 객체로 작업해라. 여기서 this는 잘보면void CRS232SwitchLEDMonitorDlg::InitUdp() 여기서 사용 됐는데 this가 CRS232SwitchLEDMonitorDlg의 주소니깐 맞는 말임
  • LPVOID는 가리키는 대상의 타입이 정해지지않았다는 뜻.
    - 여기서 왜 스레드 매개변수가 LPVOID인가?? 스레드 함수는 어떤 데이터든 받아야 하는 형태로 설정 되어야 하기 때문에 UINT ThreadFunc(LPVOID pParam); 으로 설정하면 int, 구조체, 클래스 객체도 주소만 받으면 전부 스레드 사용가능함.
  • 그래서 다이얼로그 클래스 객체의 멤버 변수들에 접근해야 스레드를 작업할 수 있는데 연결하는 법이 클래스 pDlg = (클래스)pParam 이렇게 인데 이 한 줄의 뜻을 파헤쳐 보면??
    - CRS232SwitchLEDMonitorDlg pDlg =
    (CRS232SwitchLEDMonitorDlg
    )pParam;
    => pParam안에 들어있는 주소는 클래스객체의 주소다라고 컴파일러에게 알려준다.
  • 다시 순서 정리
    1. 스레드 시작 선언 this --> [CRS232SwitchLEDMonitorDlg 객체]
    1. 스레드 함수 내부 LPVOID pParam // 그냥 주소 ( 정체도 모름 )
    2. 캐스팅 후 CRS232SwitchLEDMonitorDlg* pDlg // 이제 정체가 밝혀짐
    3. 그렇다면 pDlg 는 클래스 다이얼로그 객체의 주소( 명확한 주소 )
    4. pParam은 주소긴 주소인데 정체를 모르는 주소.

BYTE buf[1024];

  • UDP로 수신한 원시 데이터를 담는 버퍼
    - BYTE = unsigned char(1바이트)
    • 1024 = 최대 1024바이트 까지 수신가능
    • 왜 BYTE? : UDP데이터는 문자열 X 구조체 X 그냥 바이트 덩어리
    • 예시 02 01 0E 00 02 7F 8A 03
      • buf[0] = 0x02, buf[1] = 0x01

SOCKADDR_IN from;

  • 이 UDP패킷을 누가 보냈는지 저장하는 구조체
  • SOCKADDR_IN안에는 무엇이 있나?
struct sockaddr_in {
    short sin_family;      // AF_INET
    u_short sin_port;      // 송신자 포트
    struct in_addr sin_addr; // 송신자 IP
};
- **보낸** 쪽 IP주소
- **보낸** 쪽 포트번호

왜??? UDP는 연결개념이 아니라 누가 보냈는지 매번 알려줘야함 -> 수신할 때 이 패킷은 누가보낸 패킷이다를 OS가 채워줌

int fromLen = sizeof(from);

  • from 구조체 크기를 OS에 알려주는 용도
  • 왜필요?
recvfrom(
    socket,
    buf,
    len,
    0,
    (SOCKADDR*)&from,
    &fromLen
);
  • 이 함수는 from에 주소 정보를 저장하는데 얼마나 써도 되는지 미리 알아야함
BYTE buf[1024];         // 데이터 받을 공간
SOCKADDR_IN from;      // 송신자 정보
int fromLen = sizeof(from); // 송신자 정보 크기

UDP 수신 스레드의 핵심 루프
전체 루프 요약

  • UDP 수신 스레드가 살아있는 동안 계속 패킷을 받고 -> 처리하고 -> 없으면 쉰다.
  1. while(pDlg -> m_bUdpThreadRun)
  • 이 스레드를 계속 돌릴지 말지 결정하는 스위치
    - True 수신계속
    - False 스레드 종료
    • 보통 OnDestroy() 같은 곳에서 m_bUdpThreadRun = FALSE; 로 같이 꺼줌
  1. recvfrom() — UDP 수신 핵심
int recvLen = recvfrom(
    pDlg->m_udpSocket,     // 1️⃣ 사용할 UDP 소켓
    (char*)buf,            // 2️⃣ 수신 데이터 버퍼
    sizeof(buf),           // 3️⃣ 최대 수신 크기
    0,                     // 4️⃣ 옵션 (보통 0)
    (SOCKADDR*)&from,      // 5️⃣ 송신자 주소 저장
    &fromLen               // 6️⃣ 주소 구조체 크기
);

반환값 recvLen
값이 >0 : 받은 바이트 수
0: 정상적으로 받은 데이터 없음
<0: 에러

  1. if (recvLen > 0)
  • 실제로 데이터가 들어왔다는 뜻이므로 데이터가 들어왔을 때만 구분하여 처리
  1. CByteArray packet 생성
    buf는 임시 작업대로 크기가 고정되어있음(1024) 다음 recvfrom 호출때 덮어써짐
    길이 정보 없음 즉 안전하지않음 데이터
  • 그래서 packet생성하여 의미잇는 데이터 덩어리로 변환
    1. 실제 받은 길이만큼 보관가능
    1. 함수 인자로 넘기기 좋음
    2. 프로토콜 처리에 최적함
    3. 그래서 넘겨줘야함 for문으로
for (int i = 0; i < recvLen; i++)
    packet.Add(buf[i]);
  1. DebugPrintPacket()
    pDlg->DebugPrintPacket(_T("UDP RX"), packet);
    -> 수신된 UDP 패킷을 로그로 출력
    ex) [UDP RX] 02 01 0E 00 02 7F 8A 03

  2. ProcessPacket() (rs232통신할때 만든 함수)
    -> 프로토콜 해석 + 실제 동작 처리

  3. else { Sleep(1); }
    언제? recvfrom 실패, 수신데이터 없을때만

전체 흐름 그림

스레드 호출
-> while(run)
-> recvfrom()
-> 데이터 있니? (1->) 아니요 -> sleep(1)
(2->) 예 -> buf=>packet복사
-> 로그출력
-> 패킷처리


바코드 부분도 똑같음

profile
0부터 시작하는 백엔드

0개의 댓글