IRC Server Refactoring 1: C++98 to C++17

May 8, 2026


42 Seoul에서 첫 cpp 팀프로젝트였던 ft_irc는 완성했을 때 아쉬움이 많이 남는 과제였다. 아쉬움을 채우기 위해 새로운 목표로 재구성하기로 했다.


프로젝트 배경

  • 42 Seoul의 ft_irc
    • Creating your own IRC server.
    • C++98 사용 및 외부 라이브러리(Boost) 사용 불가능

요구 사항 다시 보기

  • RFC 기반 IRC 서버 기능 (연결, 채널, 모드, 메시지)
  • 채널 명령어: KICK, INVITE, TOPIC, MODE(i,t,k,o,l)
  • Non-blocking I/O, 단일 poll, fork 금지
  • 이번에는 외부 평가가 아닌 실제 사용 가능한 수준으로

기술 스택 & 설계 원칙

  • 언어: C++17 (개인적으로 Pope Kim의 C++ 프로그래밍 강의를 통해 최신 표준을 체득하고 RAII, 스마트 포인터, final 등 실전 기법을 프로젝트에 적용하기 결정)
  • 빌드: CMake
  • RAII + unique_ptr로 자원 관리
  • 에러 처리: 생성자에서는 예외(throw) 사용, I/O에서는 errno와 반환값으로 처리
  • 가상 소멸자 대신 final 클래스
  • 의존성 최소화
  • 불필요한 상태 제거

아키텍처 간단 다이어그램

[Client] --TCP--> [Server (poll loop)]
                   ├── Client objects (unique_ptr)
                   ├── Channel list
                   └── Command parser

C++98로 인한 한계 - raw 포인터와 수동 소멸

// Server.cpp (ft_irc)
Server::~Server() {
	std::map<int, Client*>::iterator it = _clients.begin();
	while (it != _clients.end())
	{
		close(it->first);
		delete it->second;
		it->second = NULL;
		++it;
	}
	std::map<std::string, Channel*>::iterator it2 = _channels.begin();
	while (it2 != _channels.end())
	{
		delete it2->second;
		it2->second = NULL;
		++it2;
	}
	_clients.clear();
	_message.clear();
	_channels.clear();
	close(_serverFd);
}

이번 프로젝트에서는 std::unique_ptr로 소유권을 자동으로 관리하도록 바꾼다.


C++98로 인한 한계 - 고정 크기 poll 배열

// Server.h (ft_irc)
struct pollfd   _pollFds[USER_MAX + 4];

클라이언트 수가 늘어나면 유연하게 대처할 수 없고, std::vector로 동적 관리가 훨씬 합리적이다.


C++98로 인한 한계 - 매크로

// Server.h (ft_irc)
#define BUF_LEN 1024
#define MSG_LEN 2048
#define USER_MAX 128

디버거에서 값 추적이 어렵고, 타입 안전하지 않다. 이번에는 constexpr 상수와 함수로 대체해 컴파일 타임 평가와 타입 안정성을 확보한다.


C++98로 인한 한계 - 시간 처리

time_t t;
time(&t);
startTime = ctime(&t);

ctime 대신 std::chrono를 쓰면 더 정밀하고, 스레드 안전하며 C++다운 코드가 된다.


왜 C++17인가?

  • unique_ptr, shared_ptr - 메모리 누수 봉쇄
  • std::vector, std::unordered_map - 동적 크기 관리, 빠른 조회
  • enum class - 스코프 구분, 암시적 형변환 방지
  • final 키워드 - 가상 소멸자를 없애고, 의도치 않은 상속을 막음
  • constexpr - 컴파일 타임에 값이 결정되므로 성능 및 안전성 향상
  • 복사/이동 연산자 = delete - 소켓 같은 자원을 복사할 수 없음을 컴파일 단계에서 강제

이번 리팩토링의 핵심

  • 자원은 생성자에서 획득하고 소멸자에서 반환한다.
  • 에러 처리는 정해진 규칙(생성자=예외, I/O=오류코드)을 따른다.

앞으로의 계획

  • poll 이벤트 루프와 std::unique_ptr 적용기
  • 모던 C++로 명령어 재구성
  • TCP_NODELAY 실험 - Nagle 알고리즘과 패킷 덤프 비교
  • TCP Keep-Alive와 PING/PONG으로 좀비 클라이언트 퇴치, Docker 배포