send() - 전송의 진짜 의미
기본 코드
const char* msg = "Hello I am client";
int len = static_cast<int>(strlen(msg));
int sent = send(clientSocket, msg, len, 0);
if (sent == SOCKET_ERROR) {
int err = WSAGetLastError();
return false;
}
반환값 해석
| 반환값 | 의미 |
|---|
> 0 | 이번 호출에서 전송 버퍼로 넘긴 바이트 수 |
SOCKET_ERROR | 실패 (WSAGetLastError 확인) |
핵심 오해 교정
send 성공은 "상대 앱이 이미 처리했다"가 아닙니다.
- 정확히는 "내 커널 송신 버퍼에 복사(또는 전송 큐 등록)되었다"에 가깝습니다.
- 따라서 애플리케이션 레벨 ACK가 필요하면 별도 프로토콜이 필요합니다.
부분 전송(Partial Send) 처리
왜 필요한가
send는 요청한 len 전부를 한 번에 보내지 못할 수 있습니다.
- 특히 non-blocking 소켓, 혼잡 구간, 큰 payload에서 자주 발생합니다.
안전한 SendAll 루프
bool SendAll(SOCKET s, const char* data, int len) {
int offset = 0;
while (offset < len) {
int n = send(s, data + offset, len - offset, 0);
if (n == SOCKET_ERROR) {
int err = WSAGetLastError();
return false;
}
if (n == 0) return false;
offset += n;
}
return true;
}
실무 포인트
- "한 번 send = 전체 전송" 가정은 버그 원인입니다.
- 전송 길이를 추적하는 루프를 기본 패턴으로 습관화하세요.
recv() - 수신의 진짜 의미
기본 코드
char recvBuffer[1024];
int recvLen = recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
if (recvLen > 0) {
} else if (recvLen == 0) {
} else {
int err = WSAGetLastError();
}
반환값 요약
| 반환 | 의미 |
|---|
> 0 | 받은 바이트 수 |
0 | 상대가 연결 종료 |
SOCKET_ERROR | 오류 발생 |
매우 중요한 사실
- TCP는 스트림이므로 패킷 경계가 없습니다.
- 즉, 한 번
send한 데이터가 여러 번 recv로 쪼개져 올 수 있고,
- 반대로 여러 번
send한 데이터가 한 번 recv에 합쳐져 올 수 있습니다.
에코 서버 구현 (안전 패턴)
서버 루프 예시
char buf[4096];
for (;;) {
int n = recv(clientSocket, buf, sizeof(buf), 0);
if (n > 0) {
if (!SendAll(clientSocket, buf, n)) break;
} else if (n == 0) {
break;
} else {
int err = WSAGetLastError();
break;
}
}
closesocket(clientSocket);
왜 에코가 좋은 실습인가
- 송수신 경로가 동시에 검증됩니다.
- 부분 전송/종료 처리/오류 로그 등 기본 네트워크 습관을 한 번에 점검할 수 있습니다.
send/recv 비대칭 실험 해석
send만 호출
- 결과: 일정 구간까지는 성공할 수 있습니다.
- 이유: 커널 송신 버퍼가 아직 여유가 있으면 복사만으로 반환되기 때문입니다.
recv만 호출
- 결과: 데이터가 없으면 블로킹 소켓은 대기합니다.
- 이유: 수신 버퍼에 데이터가 들어올 때까지 반환할 정보가 없기 때문입니다.
결론
send와 recv는 "호출 타이밍/버퍼 상태" 관점에서 동작이 비대칭입니다.
- 이 비대칭 이해가 non-blocking, 이벤트 기반 모델의 출발점입니다.
입문 단계에서 자주 하는 실수
| 실수 | 문제 | 개선 |
|---|
send 1회만 호출 | 일부 데이터 누락 가능 | SendAll 루프 사용 |
recv 데이터를 문자열로 가정 | 널 종료 보장 없음 | 길이 기반 처리 |
recv==0을 에러로 처리 | 정상 종료를 오판 | 종료 분기 별도 처리 |
| TCP에서 패킷 경계 가정 | 파싱 오류/프로토콜 깨짐 | 길이 헤더 기반 프로토콜 |
| 실패 시 소켓 미정리 | 핸들 누수 | 오류 경로마다 closesocket |
강의 시 유의사항
강조 포인트
send 성공의 의미를 정확히 정의해야 이후 파트 오해가 줄어듭니다.
- TCP 스트림 특성(경계 없음)은 초반에 반드시 못 박아야 합니다.
recv==0의 의미(정상 종료)를 분리해서 설명하세요.
시연 추천
- 큰 버퍼 전송으로 partial send 가능성 확인
- 서버 종료 시 클라이언트
recv==0 확인
- 문자열/바이너리 처리 차이(널 종단 가정 오류) 재현
체크 질문 (스스로 답해보기)
- 왜
send를 루프로 감싸야 하는가?
recv가 0을 반환하면 어떤 상태를 의미하는가?
- TCP에서 메시지 경계를 보장하려면 애플리케이션은 무엇을 추가해야 하는가?