프로그래밍/소켓 프로그래밍 입문

파일 송/수신 서비스 구조 설계

데일리 백수 2025. 1. 21. 21:38

파일 송/수신 서비스에서 중요한 핵심은 프로토콜 설계데이터 처리 방식입니다. 특히, TCP의 특성상 데이터를 스트림 형태로 송신하고, 수신 측에서는 데이터를 끊어서 처리해야 합니다. 이 글에서는 효율적인 파일 송/수신 서비스 설계를 위한 구조와 프로토콜 설계를 정리합니다.

 

 

1. 기본 개념: send와 recv의 불일치

1.1 send와 recv의 동작 차이

  • send:
    • 송신 측에서 데이터를 한 번에 보낼 수 있음.
    • 하지만 실제 네트워크에서는 이 데이터가 여러 개의 세그먼트로 분리되어 전달.
  • recv:
    • 수신 측에서 데이터를 조립하여 애플리케이션에 제공.
    • 데이터를 조립하지 못한 경우 여러 번 recv 호출이 필요.

1.2 스트림 데이터 처리

  • TCP는 데이터를 스트림으로 전달.
  • 송신 측:
    • 데이터를 순차적으로 전송하며, 스트림의 경계를 신경 쓰지 않음.
  • 수신 측:
    • 데이터를 헤더와 페이로드(Payload)로 나눠서 해석.
    • recv를 여러 번 호출해 데이터를 조립해야 함.

2. 프로토콜 설계: 헤더와 페이로드

2.1 헤더와 페이로드 구조

  • 데이터를 송신할 때는 헤더(Header)와 페이로드(Payload)로 나눔.
  • 헤더(Header):
    • 메타데이터(파일 이름, 크기 등)를 포함.
    • 수신 측이 데이터를 해석하기 위한 기본 정보.
  • 페이로드(Payload):
    • 실제 전송하려는 데이터(파일 내용 등).

2.2 헤더 설계

(1) 기본 헤더

  • 프로토콜의 기본 구조를 정의:
    • 코드 값(Code): 확장 헤더와 데이터를 구분하기 위한 식별자.
    • 데이터 크기(Size): 페이로드의 크기.
typedef struct {
    uint16_t code;    // 메시지 코드
    uint32_t size;    // 데이터 크기
} BasicHeader;

 

(2) 확장 헤더

  • 기본 헤더의 정보를 기반으로 확장 정보를 포함:
    • 파일 이름, 파일 크기 등.
  • 예시:
     
typedef struct {
    char filename[256];  // 파일 이름
    uint32_t filesize;   // 파일 크기
} FileHeader;

 

3. 송신 프로세스 설계

3.1 송신 흐름

  1. 헤더 생성:
    • 송신할 데이터를 설명하는 기본 및 확장 헤더 생성.
  2. 헤더 송신:
    • 기본 헤더와 확장 헤더를 송신.
  3. 페이로드 송신:
    • 데이터를 64KB와 같은 고정 크기로 분할하여 송신.

3.2 송신 예제

// 1. 기본 헤더 전송
BasicHeader header = { CODE_FILE_TRANSFER, filesize };
send(sock, (char*)&header, sizeof(header), 0);

// 2. 확장 헤더 전송
FileHeader fileHeader = { "example.txt", filesize };
send(sock, (char*)&fileHeader, sizeof(fileHeader), 0);

// 3. 파일 데이터 송신
while ((bytesRead = fread(buffer, 1, BUFF_SIZE, file)) > 0) {
    send(sock, buffer, bytesRead, 0);
}

 

4. 수신 프로세스 설계

4.1 수신 흐름

  1. 헤더 수신:
    • 기본 헤더를 수신하여 확장 헤더와 데이터를 구분.
  2. 확장 헤더 수신:
    • 확장 정보를 수신하고, 파일을 생성하거나 저장 공간 확보.
  3. 데이터 수신:
    • 페이로드를 읽어 파일에 저장.
// 1. 기본 헤더 수신
BasicHeader header;
recv(sock, (char*)&header, sizeof(header), 0);

// 2. 확장 헤더 수신
FileHeader fileHeader;
recv(sock, (char*)&fileHeader, sizeof(fileHeader), 0);

// 3. 파일 데이터 수신
FILE* file = fopen(fileHeader.filename, "wb");
while ((bytesRead = recv(sock, buffer, BUFF_SIZE, 0)) > 0) {
    fwrite(buffer, 1, bytesRead, file);
}
fclose(file);

 

5. 효율적인 프로토콜 설계 고려사항

5.1 데이터 단위 설계

  • 헤더-페이로드 방식:
    • 데이터를 단위별로 정의해 수신 측에서 쉽게 해석 가능.
  • 코드 값 활용:
    • 코드 값으로 데이터 유형을 구분:
      • 예: CODE_FILE_TRANSFER, CODE_HEARTBEAT.

5.2 성능 최적화

  1. 버퍼 크기 설정:
    • 송수신 버퍼 크기를 적절히 설정(예: 64KB).
    • 너무 작은 크기는 성능 저하, 너무 큰 크기는 메모리 낭비.
  2. 송신과 수신의 동기화:
    • 수신 속도가 송신 속도를 따라잡을 수 있도록 설계.
  3. 네트워크 장애 처리:
    • 타임아웃 및 재전송 메커니즘 구현.

5.3 데이터 무결성 검증

  • 해시 값 사용:
    • MD5, SHA256 등의 해시 값을 전송해 데이터 무결성 확인.
  • ACK 기반 확인:
    • 수신 측에서 송신 측으로 **확인 응답(ACK)**을 보내 데이터 손실 확인.