데이터 송수신과 에코 서버

Jaemyeong Lee·2025년 1월 8일

게임 서버1

목록 보기
124/220

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) {
    // recvLen 바이트만 유효
} else if (recvLen == 0) {
    // 상대의 정상 종료(FIN)
} 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만 호출

  • 결과: 데이터가 없으면 블로킹 소켓은 대기합니다.
  • 이유: 수신 버퍼에 데이터가 들어올 때까지 반환할 정보가 없기 때문입니다.

결론

  • sendrecv는 "호출 타이밍/버퍼 상태" 관점에서 동작이 비대칭입니다.
  • 이 비대칭 이해가 non-blocking, 이벤트 기반 모델의 출발점입니다.

입문 단계에서 자주 하는 실수

실수문제개선
send 1회만 호출일부 데이터 누락 가능SendAll 루프 사용
recv 데이터를 문자열로 가정널 종료 보장 없음길이 기반 처리
recv==0을 에러로 처리정상 종료를 오판종료 분기 별도 처리
TCP에서 패킷 경계 가정파싱 오류/프로토콜 깨짐길이 헤더 기반 프로토콜
실패 시 소켓 미정리핸들 누수오류 경로마다 closesocket

강의 시 유의사항

강조 포인트

  • send 성공의 의미를 정확히 정의해야 이후 파트 오해가 줄어듭니다.
  • TCP 스트림 특성(경계 없음)은 초반에 반드시 못 박아야 합니다.
  • recv==0의 의미(정상 종료)를 분리해서 설명하세요.

시연 추천

  • 큰 버퍼 전송으로 partial send 가능성 확인
  • 서버 종료 시 클라이언트 recv==0 확인
  • 문자열/바이너리 처리 차이(널 종단 가정 오류) 재현

체크 질문 (스스로 답해보기)

  • send를 루프로 감싸야 하는가?
  • recv가 0을 반환하면 어떤 상태를 의미하는가?
  • TCP에서 메시지 경계를 보장하려면 애플리케이션은 무엇을 추가해야 하는가?

profile
李家네_공부방

0개의 댓글