커널 버퍼가 필요한 이유
큰 그림
App(send/recv) <-> 커널 송수신 버퍼 <-> NIC/네트워크 <-> 상대 커널 버퍼 <-> 상대 App
- 애플리케이션 속도와 네트워크 속도는 항상 다릅니다.
- 커널 버퍼는 이 속도 차이를 흡수하는 완충 장치 역할을 합니다.
- 그래서
send/recv는 "즉시 전송/즉시 수신"이 아니라 "버퍼와 상호작용"입니다.
소켓별 버퍼
| 항목 | 설명 |
|---|
| 송신 버퍼 | 앱이 보낸 데이터를 임시 저장 후 OS가 전송 |
| 수신 버퍼 | 네트워크에서 받은 데이터를 앱이 recv하기 전 저장 |
| 위치 | 커널 영역(앱 버퍼와 별개) |
핵심 결론
- 네트워크 API를 잘 쓰려면 "함수 호출"보다 "버퍼 상태"를 먼저 생각해야 합니다.
send() 경로와 반환값 해석
동작 흐름
- 앱 버퍼 -> 커널 송신 버퍼 복사 시도
- 복사된 바이트 수를 반환
- 실제 선로 전송은 OS가 백그라운드 수행
반환값 의미
| 반환 | 의미 |
|---|
> 0 | 이번 호출에서 큐잉/복사된 바이트 수 |
SOCKET_ERROR | 실패 (WSAGetLastError 확인) |
블로킹 vs 논블로킹
- 블로킹 소켓: 버퍼 여유가 생길 때까지 대기할 수 있음
- 논블로킹 소켓: 즉시 실패(
WSAEWOULDBLOCK)할 수 있음
int n = send(s, data, len, 0);
if (n == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK) {
} else {
}
}
recv() 경로와 반환값 해석
동작 흐름
- 커널 수신 버퍼에 데이터가 있는지 확인
- 있으면 요청한 최대 길이만큼 앱 버퍼로 복사
- 없으면 블로킹 대기 또는
WSAEWOULDBLOCK
반환값 요약
| 반환 | 의미 |
|---|
> 0 | 복사된 바이트 수 |
0 | 상대가 정상 종료(FIN) |
SOCKET_ERROR | 오류 발생 |
중요한 성질
- TCP는 스트림이므로 경계가 없습니다.
- 한 번의
recv가 "내가 기대한 메시지 1개"를 정확히 주는 보장은 없습니다.
- 따라서 상위 프로토콜(길이 헤더/패킷 파서)이 필수입니다.
흐름 제어(Flow Control)와 백프레셔
왜 send가 막히는가
- 상대가
recv를 늦게 하면 상대 수신 버퍼가 차고,
- TCP 윈도우 광고가 줄어들어 송신 측 전송량이 제한됩니다.
- 결과적으로 송신 측
send도 지연/실패(WOULDBLOCK)가 증가합니다.
실무에서 보이는 현상
| 현상 | 관찰 포인트 |
|---|
| 송신 큐 길이 증가 | 앱 내부 pending send queue가 계속 쌓임 |
| 지연 증가 | RTT/응답시간 상승 |
| 메모리 증가 | 큐잉된 패킷 누적 |
| disconnect 증가 | 타임아웃/정책에 의한 연결 정리 |
대응 원칙
- 무제한 큐잉 금지(최대 pending 바이트 설정)
- 임계치 초과 시 drop 또는 연결 종료 정책
- 송신 우선순위/압축/배치 전략 적용
버퍼 크기와 튜닝 관점
기본 인식
- "기본 버퍼 크기 = 정답"이 아닙니다.
- 트래픽 패턴(짧은 패킷 다량 vs 큰 데이터 전송)에 따라 최적값이 달라집니다.
옵션 예시
| 옵션 | 의미 | 주의점 |
|---|
SO_SNDBUF | 송신 버퍼 크기 힌트 | 크게 하면 메모리 사용 증가 |
SO_RCVBUF | 수신 버퍼 크기 힌트 | 크게 하면 지연/메모리 trade-off |
튜닝 방법
- 감으로 조정하지 말고 지표(지연, 드롭, 큐 길이) 기반으로 변경
- 변경 후 부하 테스트로 회귀 검증
운영 관측 지표(필수)
최소 수집 항목
- 초당 send/recv 바이트
WSAEWOULDBLOCK 발생 횟수
- pending send queue 길이(세션별/전체)
recv==0 종료 비율, 소켓 오류 코드 분포
장애 시 빠른 판단
- 특정 구간에서만 큐가 쌓이는가?
- 전체 세션에서 동시에 지연이 증가하는가?
- 네트워크 문제인지 애플리케이션 소비 속도 문제인지 분리 가능한가?
강의 시 유의사항
강조 포인트
- 버퍼 메커니즘은 "send/recv 함수 설명"이 아니라 시스템 동작 모델입니다.
send 성공 의미와 recv 반환값 0 의미를 반드시 구분해 설명하세요.
- 흐름 제어(backpressure)를 이해해야 이후 non-blocking/IOCP 설계가 자연스럽습니다.
자주 하는 오해
| 오해 | 바로잡기 |
|---|
| send 성공이면 상대가 이미 처리했다 | 커널 송신 버퍼 등록 성공에 가깝다 |
| recv는 한 번에 메시지 단위로 온다 | TCP는 스트림이므로 경계 없음 |
| 버퍼를 크게 하면 무조건 좋다 | 메모리/지연/운영 정책 trade-off 존재 |
체크 질문 (스스로 답해보기)
WSAEWOULDBLOCK이 자주 발생할 때 무엇을 먼저 점검해야 하는가?
- 왜 앱 레벨에서 길이 기반 패킷 파싱이 필요한가?
- pending 송신 큐에 상한을 두지 않으면 어떤 장애가 생길 수 있는가?