IRC Server Refactoring 1: C++98 to C++17
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 배포