포트폴리오/[IoT 기반 피지컬 AI 교육일지]

[ C++ 멀티스레드 TCP 서버 ] 데이터 구조화 및 스레드 동기화 (22일차 기록)

개발자혜콩 2026. 3. 11. 09:08

1. 오늘 한 것

① TCP 클라이언트 제어 및 종료 관리

  • Half-close (반쪽 닫기): 송신이나 수신 중 한쪽 스트림만 먼저 닫아 안전하게 통신을 종료하는 기법 실습.
  • 메모리 누수 방지: 사용이 끝난 소켓은 반드시 closesocket()으로 닫아주어 시스템 리소스가 낭비되지 않도록 처리.
  • UDP와 멀티스레드: UDP는 연결 지향이 아니라서 스레드가 필수는 아니지만, 데이터 손실 가능성을 대비해 결국 멀티스레드 구현이 동반되어야 유리함을 확인.

② 프로젝트 기획 및 아키텍처 설계 기준

  • 개발 스택: TCP/IP 멀티스레드 통신, 아두이노 직렬 통신(Serial), 리눅스 MariaDB 서버 구축, 윈도우 클라이언트(GUI 필수).
  • 데이터 모델링(구조화): 코딩을 시작하기 전, 주고받을 패킷 구조를 먼저 명확히 설계해야 함. 데이터 구조를 짤 때, 변수명이 길어지면 번거로우니 Quantity 대신 Qty로 직관적이고 짧게 선언하는 방식도 적극 활용할 예정.

③ 통신 프로토콜 및 시스템 프로그래밍 팁

  • NMEA 0183: 국제해사기구(IMO)에서 제정한 해양 통신 표준 프로토콜. 독자적인 통신 규격을 만들기보다, 이런 표준 프로토콜 구조를 참고해 설계하면 확장성이 좋아짐.
  • 백엔드(소켓) 통신의 장점: URL 구조(HTTP API 등)에 의존하지 않고 패킷 단위로 직접 소통하므로 재사용성이 용이함.
  • 공간 낭비 최적화 (#pragma pack): 구조체 패딩(Padding)으로 인해 생기는 빈 공간을 없애 메모리를 꽉 채워주는 기능. 로켓이나 임베디드 기기처럼 한 바이트가 아쉬운 환경에서 통신 패킷을 만들 때 유용함.
  • 터미널 세팅: Character set을 변경하여 터미널에서 한글이 깨지지 않게 설정.
  • 헤더 관리: #include "../window/Common.h"와 같이 상대 경로를 활용한 공통 헤더 파일 참조.

★★★★ 멀티스레드 TCP 서버 ★★★★

  • 주소 정보 확인 (getpeername): 현재 통신 중인 상대방(Client)의 IP 주소와 포트 정보를 소켓에서 역으로 알아내는 함수.
  • 스레드 동기화 기법 필수 사용: 멀티스레드 환경에서는 여러 스레드가 동시에 접근할 때 발생하는 문제를 막기 위해 동기화가 필수적임을 학습.
     
  • 실습 진행: 코드 6-4(멀티스레드 TCP 서버)와 코드 6-5(임계 영역)를 단순히 복사하지 않고 직접 타이핑하며 흐름 파악.

2. 문제 / 헷갈린 점

① 스레드 전역변수 충돌 (Race Condition):

    • 멀티스레드에서 전역변수를 조심해야 한다는 말이 와닿지 않았음.
       
    • 해결 및 깨달음: 여러 스레드가 동시에 하나의 전역변수(공유 자원)에 접근해 값을 수정하면, 값이 덮어씌워져 데이터가 꼬이는 현상(Race Condition)이 발생함. 이를 막기 위해 한 스레드가 변수를 쓰는 동안 다른 스레드가 못 건드리게 문을 잠그는 '뮤텍스(Mutex)''임계 영역(Critical Section)' 같은 상호배제 동기화 기법을 반드시 써야 함.

 

스레드 생성과 함수 포인터, 그리고 LPVOID의 의미:

  • 스레드를 만들 때 왜 lpStartAddress 같은 주소가 필요한지, LPVOID는 왜 쓰는지 궁금했음.
  • 해결  : C/C++에서 '함수 이름'은 곧 그 함수가 메모리상에 존재하는 시작 주소를 의미함. 운영체제에 스레드를 만들 때 "이 주소(함수)부터 실행해!"라고 알려주는 것. LPVOID (Long Pointer Void, void*)는 정수, 구조체 등 어떤 형태의 데이터든 가리킬 수 있는 만능 포인터라서, 스레드에 다양한 소켓 정보나 구조체를 유연하게 넘겨주기 위해 사용함.

 

특정 스레드의 CPU 독점 현상과 Sleep():

  • t1(수신), t2(송신), t3(키보드) 스레드가 동시에 돌 때 데이터가 몰리면, 하나의 스레드가 CPU를 독점해서 다른 기능이 먹통이 되는 걸 어떻게 처리해야 할지 고민됨. 강제 종료(Ctrl + C)를 쓰면 안전하게 소켓을 닫지 못해 문제가 됨.
  • 해결: 작업 중간중간 Sleep() 함수를 넣어 일시 중지(Yield) 시켜주면, CPU 제어권을 다른 대기 중인 스레드에게 양보하게 되어 프로그램이 매끄럽게 돌아감. 종료할 때도 강제 종료 대신, 안전하게 탈출 플래그를 세워 루프를 빠져나오게 설계해야 함.

3. 오늘 배운 핵심

  • 멀티스레드 TCP 서버는 '스레드 동기화(Mutex/Critical Section)'가 생명이다. 전역변수 남용을 피하고, 피할 수 없다면 반드시 자원에 자물쇠를 채워야 한다.
  • 멀티스레드는 CPU를 나눠 쓰는 기술이므로, Sleep()을 통한 적절한 양보와 제어가 필수다.
  • 통신 코딩을 시작하기 전, NMEA 규격이나 #pragma pack을 활용한 철저한 '데이터 모델링(구조화)'이 선행되어야 한다.