본문 바로가기

EM(Embedded system)

wget 방식의 파일 다운로드 코드 (https 사용불가)

728x90
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

#define BUFFER_SIZE 4096
#define OUTPUT_FILE "downloaded_file.txt"

// 오류 메시지 출력 후 종료
void error_exit(const char *message) {
    perror(message);
    exit(EXIT_FAILURE);
}

// URL에서 호스트와 리소스 추출
void parse_url(const char *url, char *host, char *resource) {
    if (strncmp(url, "http://", 7) == 0) {
        url += 7;  // "http://" 스킵
    } else if (strncmp(url, "https://", 8) == 0) {
        fprintf(stderr, "HTTPS는 지원되지 않습니다.\n");
        exit(EXIT_FAILURE);
    }

    char *slash = strchr(url, '/');
    if (slash) {
        strncpy(host, url, slash - url);
        host[slash - url] = '\0'; // NULL 종료
        strcpy(resource, slash);
    } else {
        strcpy(host, url);
        strcpy(resource, "/"); // 기본값으로 루트 설정
    }
}

int main() {
    char url[256], host[128], resource[128];

    // 사용자로부터 URL 입력받기
    printf("다운로드할 URL을 입력하세요: ");
    scanf("%255s", url);

    // URL 파싱
    parse_url(url, host, resource);
    printf("HOST: %s\n", host);
    printf("RESOURCE: %s\n", resource);

    int sock;
    struct sockaddr_in server_addr;
    struct hostent *server;
    char request[1024], buffer[BUFFER_SIZE];
    FILE *file;

    // DNS 조회
    server = gethostbyname(host);
    if (!server) {
        error_exit("DNS 조회 실패");
    }

    // 소켓 생성
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        error_exit("소켓 생성 실패");
    }

    // 서버 주소 설정
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(80);
    memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);

    // 서버에 연결
    if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        error_exit("서버 연결 실패");
    }

    // HTTP GET 요청 작성
    snprintf(request, sizeof(request),
             "GET %s HTTP/1.1\r\n"
             "Host: %s\r\n"
             "Connection: close\r\n"
             "User-Agent: Custom-Wget/1.0\r\n"
             "\r\n",
             resource, host);

    // 요청 전송
    if (send(sock, request, strlen(request), 0) < 0) {
        error_exit("요청 전송 실패");
    }

    // 파일 열기
    file = fopen(OUTPUT_FILE, "wb");
    if (!file) {
        error_exit("파일 열기 실패");
    }

    // 응답 수신 및 파일 저장
    int bytes_received;
    int header_skipped = 0;
    while ((bytes_received = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
        if (!header_skipped) {
            // HTTP 헤더 제거
            char *body = strstr(buffer, "\r\n\r\n");
            if (body) {
                body += 4;  // "\r\n\r\n" 이후의 데이터부터 저장
                fwrite(body, 1, bytes_received - (body - buffer), file);
                header_skipped = 1;
            }
        } else {
            fwrite(buffer, 1, bytes_received, file);
        }
    }

    if (bytes_received < 0) {
        error_exit("데이터 수신 실패");
    }

    // 정리
    fclose(file);
    close(sock);
    printf("파일 다운로드 완료: %s\n", OUTPUT_FILE);

    return 0;
}

 

✅ 실행 방법:

  1. 컴파일:
    gcc -o wget_clone wget_clone.c
    
  2. 실행 후 URL 입력:
    ./wget_clone
    
    다운로드할 URL을 입력하세요: http://example.com/sample.txt
    HOST: example.com
    RESOURCE: /sample.txt
    파일 다운로드 완료: downloaded_file.txt
    

 

 

 

📌 wget 명령어 방식의 파일 다운로드를 C로 구현한 코드 설명

이 코드는 사용자가 입력한 URL을 분석하여 HTTP 요청을 보내고, 파일을 다운로드하여 저장하는 기능을 수행합니다.
아래에서 코드의 각 부분을 자세히 설명하겠습니다.


1️⃣ 주요 기능 개요

사용자로부터 다운로드할 URL을 입력받음
URL을 분석하여 HOST(도메인)과 RESOURCE(경로)를 분리
소켓을 이용해 HTTP 서버에 직접 연결
HTTP GET 요청을 전송하여 파일 다운로드
받은 HTTP 응답에서 헤더를 제거하고, 실제 파일 내용을 저장


2️⃣ 코드 구조 분석

🔹 1. 헤더 파일 포함

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
  • stdio.h, stdlib.h → 파일 I/O 및 기본 함수 사용
  • string.h → 문자열 처리 (strchr, strcpy 등)
  • unistd.h → close() 함수 사용
  • arpa/inet.h, netdb.h → 네트워크 관련 함수 (socket(), connect(), gethostbyname()) 사용

🔹 2. 오류 처리 함수

void error_exit(const char *message) {
    perror(message);
    exit(EXIT_FAILURE);
}
  • 오류 발생 시 해당 오류 메시지를 출력하고 프로그램을 종료합니다.
  • perror()는 시스템 오류 메시지를 출력하는 함수입니다.

예제) error_exit("파일 열기 실패");
🔹 출력 예시:

파일 열기 실패: No such file or directory

🔹 3. URL에서 HOST와 RESOURCE를 분리하는 함수

void parse_url(const char *url, char *host, char *resource) {
    if (strncmp(url, "http://", 7) == 0) {
        url += 7;  // "http://" 부분을 건너뜀
    } else if (strncmp(url, "https://", 8) == 0) {
        fprintf(stderr, "HTTPS는 지원되지 않습니다.\n");
        exit(EXIT_FAILURE);
    }

    char *slash = strchr(url, '/');  // '/' 위치 찾기
    if (slash) {
        strncpy(host, url, slash - url);
        host[slash - url] = '\0';  // 문자열 종료
        strcpy(resource, slash);
    } else {
        strcpy(host, url);
        strcpy(resource, "/");  // 기본 경로 설정
    }
}

📌 기능

  • 사용자가 입력한 URL을 분석하여 HOST(도메인)과 RESOURCE(경로)를 분리합니다.
  • 지원하는 프로토콜: HTTP만 가능 (HTTPS는 직접 구현해야 함).
  • strchr(url, '/') → 첫 번째 / 위치를 찾고, 도메인과 리소스를 나눕니다.

📌 예제 실행

입력 URL:

http://example.com/files/sample.txt

출력:

HOST: example.com
RESOURCE: /files/sample.txt

🔹 4. 사용자 입력 받기

printf("다운로드할 URL을 입력하세요: ");
scanf("%255s", url);
  • scanf("%255s", url); → URL을 입력받음 (255 글자로 제한)
  • 메모리 초과 방지를 위해 scanf("%s", url); 대신 길이 제한 추가

🔹 5. 서버 IP 조회 (gethostbyname)

server = gethostbyname(host);
if (!server) {
    error_exit("DNS 조회 실패");
}
  • 도메인(호스트명) → IP 주소로 변환 (gethostbyname())
  • 예제) example.com → 93.184.216.34
  • 도메인이 잘못되었거나 인터넷 연결이 없으면 오류 발생

🔹 6. TCP 소켓 생성 및 서버 연결

sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
    error_exit("소켓 생성 실패");
}
  • socket(AF_INET, SOCK_STREAM, 0) → TCP 소켓 생성
  • 실패 시 프로그램 종료
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80);  // HTTP 기본 포트 (80)
memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);
  • htons(80) → HTTP는 기본적으로 80번 포트를 사용
  • memcpy() → server->h_addr에서 실제 IP를 server_addr에 복사
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
    error_exit("서버 연결 실패");
}
  • 서버에 연결 시도
  • 실패하면 프로그램 종료

🔹 7. HTTP GET 요청 전송

snprintf(request, sizeof(request),
         "GET %s HTTP/1.1\r\n"
         "Host: %s\r\n"
         "Connection: close\r\n"
         "User-Agent: Custom-Wget/1.0\r\n"
         "\r\n",
         resource, host);
  • HTTP GET 요청을 생성
  • snprintf()를 사용하여 리소스 경로 및 호스트를 포함한 요청 문자열 생성
  • 예제 요청:
GET /files/sample.txt HTTP/1.1
Host: example.com
Connection: close
User-Agent: Custom-Wget/1.0
  • send()를 이용해 요청 전송

🔹 8. 서버 응답을 파일로 저장

file = fopen(OUTPUT_FILE, "wb");
if (!file) {
    error_exit("파일 열기 실패");
}
  • 응답 데이터를 저장할 파일을 열기 (wb = 바이너리 모드)
while ((bytes_received = recv(sock, buffer, BUFFER_SIZE, 0)) > 0) {
    if (!header_skipped) {
        char *body = strstr(buffer, "\r\n\r\n");
        if (body) {
            body += 4;  // "\r\n\r\n" 이후의 데이터부터 저장
            fwrite(body, 1, bytes_received - (body - buffer), file);
            header_skipped = 1;
        }
    } else {
        fwrite(buffer, 1, bytes_received, file);
    }
}
  • HTTP 헤더 제거
    • HTTP 응답에서 \r\n\r\n 이후부터 파일 내용이 시작됨
    • strstr(buffer, "\r\n\r\n")을 사용하여 헤더의 끝을 찾고 파일에 저장

🔹 9. 파일 닫기 및 소켓 종료

fclose(file);
close(sock);
printf("파일 다운로드 완료: %s\n", OUTPUT_FILE);
  • 파일을 닫고(fclose()) 소켓을 종료(close())

3️⃣ 실행 예시

$ ./wget_clone
다운로드할 URL을 입력하세요: http://example.com/sample.txt
HOST: example.com
RESOURCE: /sample.txt
파일 다운로드 완료: downloaded_file.txt
  • downloaded_file.txt 파일이 정상적으로 생성됨 ✅

4️⃣ 확장 가능 기능

HTTPS 지원 추가 (OpenSSL 사용)
파일 이름 자동 설정 (basename() 활용)
다중 스레드 다운로드 (병렬 처리)
진행률 표시 (printf("\r다운로드 중: %d KB", total_bytes / 1024))

 
728x90