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;
}
✅ 실행 방법:
- 컴파일:
gcc -o wget_clone wget_clone.c - 실행 후 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
'EM(Embedded system)' 카테고리의 다른 글
| [linux] gcc 버전 확인 (0) | 2024.07.18 |
|---|---|
| [linux] 압축(tar, tar.gz, tar.xz) 및 압축 풀기 (0) | 2024.07.18 |
| [linux] 쉘 명령어 wget - 파일 다운로드 (0) | 2024.07.18 |
| SD CARD format to FAT32 (0) | 2024.07.16 |
| OS image write on SD CARD (0) | 2024.07.16 |