왜 Select가 등장했는가
논블로킹 단독의 한계
- 논블로킹으로 바꾸면 블로킹은 사라지지만, 준비되지 않은 소켓을 계속 확인하는 busy polling 문제가 남습니다.
- 연결 수가 늘수록 "아무 일도 없는 소켓 확인 비용"이 커져 CPU를 낭비합니다.
Select의 핵심 아이디어
- "지금 읽기/쓰기 가능한 소켓만 알려달라"를 OS에 요청합니다.
- 준비된 소켓에만
accept/recv/send를 수행해 불필요한 호출을 줄입니다.
- 즉, 논블로킹에서 이벤트 기반으로 넘어가는 첫 단계입니다.
fd_set과 select() 동작 규약
fd_set 기본
- 관찰할 소켓 집합을 담는 구조입니다.
- 매크로:
FD_ZERO, FD_SET, FD_CLR, FD_ISSET
반환값 해석
select 반환값 | 의미 |
|---|
> 0 | 준비된 소켓 개수 |
0 | 타임아웃(준비된 소켓 없음) |
SOCKET_ERROR | 오류 (WSAGetLastError) |
매우 중요한 규칙
select 호출 후 fd_set 내용은 준비된 소켓만 남도록 수정됩니다.
- 따라서 다음 루프에서 재사용하면 안 되고, 매번 새로 구성(또는 master set 복사)해야 합니다.
- Windows에서
select의 첫 번째 인자(nfds)는 무시됩니다.
Select 루프 기본 패턴
권장 코드 골격
fd_set masterRead;
FD_ZERO(&masterRead);
FD_SET(listenSock, &masterRead);
for (auto& s : sessions) FD_SET(s.socket, &masterRead);
while (running) {
fd_set readSet = masterRead;
timeval tv{};
tv.tv_sec = 0;
tv.tv_usec = 10000;
int ret = select(0, &readSet, nullptr, nullptr, &tv);
if (ret == SOCKET_ERROR) {
int err = WSAGetLastError();
break;
}
if (ret == 0) {
continue;
}
if (FD_ISSET(listenSock, &readSet)) {
}
for (auto& s : sessions) {
if (FD_ISSET(s.socket, &readSet)) {
}
}
}
해석 포인트
- 리스너 소켓이 read-ready면
accept 가능 신호입니다.
- 연결 소켓이 read-ready면 데이터 수신 가능 또는 연결 종료 상태일 수 있습니다.
select가 준비를 보장해도 recv 결과는 >0/0/SOCKET_ERROR로 다시 분기해야 합니다.
read/write/except 집합 이해
| 집합 | 의미 | 대표 사용 |
|---|
readfds | 읽기 가능 | accept, recv |
writefds | 쓰기 가능 | 비동기 connect 완료 확인, send 가능 시점 |
exceptfds | 예외 상태 | OOB/오류 감지(Windows에서는 주로 오류 확인 보조) |
실무 팁
- 처음에는
readfds 중심으로 구현하고, 필요할 때 writefds를 확장합니다.
- 논블로킹 connect를 쓸 때는
writefds 확인이 중요합니다.
성능 한계와 규모 확장 문제
구조적 한계
fd_set 크기 제한(Windows 기본 FD_SETSIZE=64)이 있습니다.
- 매 루프마다 집합 재구성 + 선형 순회(O(n)) 비용이 듭니다.
- 연결 수가 많을수록 유휴 소켓 검사 비용이 커집니다.
대응 방향
| 규모 | 권장 모델 |
|---|
| 소규모/학습 | select |
| 중간 규모 | WSAEventSelect/poll 계열 |
| 대규모 동접 | IOCP (Windows), epoll/kqueue (타 OS) |
결론
- Select는 "최종 해법"보다 "이벤트 기반 사고를 익히는 교재용/입문용 모델"로 보는 것이 정확합니다.
강의 시 유의사항
강조 포인트
- Select의 본질은 Reactor 패턴의 첫 구현입니다.
fd_set이 호출 후 변경된다는 규칙을 반드시 실습으로 확인시키세요.
- "Select를 왜 버리고 IOCP로 가는지"를 성능 관점으로 연결하세요.
자주 하는 오해
| 오해 | 바로잡기 |
|---|
| 논블로킹이면 select 불필요 | busy polling 비용 때문에 필요 |
select 한 번 후 fd_set 재사용 가능 | 호출 후 내용이 바뀌므로 재구성 필요 |
| read-ready면 무조건 recv 성공 | 종료/오류 상태도 포함 가능 |
체크 질문 (스스로 답해보기)
- 왜
master set과 working set을 분리해야 하는가?
select가 0을 반환했을 때 서버 루프는 어떻게 동작해야 하는가?
- Select의 구조적 한계가 IOCP 도입으로 이어지는 이유는 무엇인가?