이 영역을 누르면 첫 페이지로 이동
웬디의 기묘한 이야기 블로그의 첫 페이지로 이동

웬디의 기묘한 이야기

페이지 맨 위로 올라가기

[C/C++] InternetReadFile Simple File Download.

웬디의 기묘한 이야기

[C/C++] InternetReadFile Simple File Download.

  • 2020.03.04 23:02
  • ⌨ DEVELOPMENT/C++
반응형

 

복잡한 Socket 통신을 하지 않고 인터넷 URL 또는 Network에 공유된 파일을 다운로드하려면 어떻게 하면 좋을까요? MSDN에 보시면 아주 간단하게 사용할 수 있는 InternetReadFile API를 제공하고 있습니다. 해당 API는 파일을 다룰 때 사용하는 ReadFile과 매우 유사하게 동작하기 때문에 파일을 조금 다루어보셨다면 어렵지 않게 다룰 수 있을 거예요.

사용 전 가장 먼저 해주어야 할 행동은 역시 header를 include 해주는 것인데요, wininet을 사용하므로 lib로 링크해주어야 합니다. 이제 아주 간단한 방법으로 URL에 있는 파일을 로컬 파일로 저장하도록 해봅시다.

 

주의 : wininet을 사용하는 경우 서버 또는 service process에는 사용하면 안 됩니다. 서버 또는 service에서 구현하는 경우 Microsoft Windows HTTP Services (WinHTTP)를 이용해야 합니다.

 

file_donwloader.hpp

class header는 간단하게 initialize()를 이용하여 HINTERNET을 초기화시켜주도록 하고 download를 통해 url 정보를 save_path\save_file_name 형태로 저장하도록 구성했습니다. 물론 소멸자에선 HINTERNET Handle을 종료해주어야겠죠?

 

 

//
// header include
//
#include <wininet.h>
//
// lib link.
//
#pragma comment(lib, "Wininet.lib")
//
// file_donwloader.
//
class file_donwloader {
public:
file_donwloader();
~file_donwloader();
public:
bool download(
__in std::wstring url,
__in std::wstring save_path,
__in std::wstring save_file_name
);
private:
void initialize();
private:
HINTERNET _internet_handle;
};

 

 

file_downloader.cpp

간단한 샘플을 작성해보았습니다. 동기 형태로 동작하며, 지정된 URL 파일을 로컬 파일로 저장하는 코드입니다. 처음엔 조금 복잡해보일수도 있지만 예외처리가 들어가있어서 그래보이는것일 뿐 정말 간단한 코드 입니다.

 

#include "file_downloader.hpp"
file_donwloader::file_donwloader() {
_internet_handle = nullptr;
initialize();
}
file_donwloader::~file_donwloader() {
/*
생성한 Handle은 반드시 소멸해주어야하기때문에 잊지않도록 소멸자에 등록합니다.
*/
if (nullptr != _internet_handle) {
InternetCloseHandle(_internet_handle);
_internet_handle = nullptr;
}
}
void file_donwloader::initialize() {
/*
InternetOpen API를 이용하여 HINTERNET Handle을 초기화 합니다.
Agent값은 아무런 값이나 입력해도 되며, HTTP 프로토콜에서는 사용자 Agent로 사용되기도 합니다.
*/
_internet_handle = InternetOpen(
L"file_donwload",
INTERNET_OPEN_TYPE_DIRECT,
nullptr,
nullptr,
0
);
if (nullptr == _internet_handle) {
__debug_printf_trace("InternetOpen failed.");
}
}
bool file_donwloader::download(
__in std::wstring url,
__in std::wstring save_path,
__in std::wstring save_file_name
) {
bool result = false;
do {
/*
넘어온 parameter는 verify check를 해주어야겠죠? 필수 입력값이기때문에
셋중에 하나라도 미입력시 정상적으로 파일을 다운로드하지 못하거나 저장하지 못할 수 있습니다.
*/
if (nullptr == _internet_handle) {
__debug_printf_trace("_internet_handle nullptr.");
break;
}
if (url.empty()) {
__debug_printf_trace("empty url.");
break;
}
if (save_path.empty()) {
__debug_printf_trace("empty save_path.");
break;
}
if (save_file_name.empty()) {
__debug_printf_trace("save_file_name save_path.");
break;
}
/*
InternetOpenUrl API를 이용하여 실제 접속이 시작됩니다.
캐시가 아닌 원본파일을 항상 다운로드받기위해 Flag는 INTERNET_FLAG_RELOAD를 주었습니다.
다른 Flag를 설정하고싶은경우 MSDN을 참조하시면 됩니다.
*/
const HINTERNET open_url_handle = InternetOpenUrl(
_internet_handle,
url.c_str(),
nullptr,
0,
INTERNET_FLAG_RELOAD,
0
);
if (nullptr == open_url_handle) {
__debug_printf_trace("_open_url_handle nullptr.");
break;
}
/*
해당 URL에 404, 500 등 http error가 넘어오더라도 정상적인 URL정보이기때문에 InternetOpenUrl의 HINTERNET Handle이 넘어오게 됩니다.
그렇기때문에 NULL 또는 INVALID_HANDLE 정보로는 에러 여부를 확인할 수 없으므로
HttpQueryInfo API를 이용하여 HTTP_QUERY_STATUS_CODE를 확인합니다.
HTTP_STATUS_OK가 넘어왔다면 접속이 성공한것이니 이제 파일로 저장하기만 하면 됩니다.
*/
DWORD status_code = 0;
DWORD status_code_size = sizeof(status_code);
if (FALSE == HttpQueryInfo(
open_url_handle,
HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
&status_code,
&status_code_size,
nullptr
)) {
__debug_printf_trace("HttpQueryInfo failed (%d)", ::GetLastError());
break;
}
if (sizeof(status_code) != status_code_size) {
__debug_printf_trace("status_code != status_code_size failed.");
break;
}
if (HTTP_STATUS_OK != status_code) {
__debug_printf_trace("error status_code (%d).", status_code);
break;
}
//
// make save file name.
//
std::wstring file_name;
file_name.assign(save_path);
file_name.append(L"\\");
file_name.append(save_file_name);
const int max_buffer_size = 2014;
char buffer[max_buffer_size] = {};
CFile save_file;
if (save_file.Open(
file_name.c_str(),
CFile::modeCreate | CFile::modeReadWrite
)) {
//
// set result.
//
result = true;
DWORD number_of_bytes_read = 0;
do {
/*
여기가 가장 중요한 부분인데요 네트워크 통신이라는게 패킷단위로 왔다갔다 하다보니 한번의 통신으로 파일을 받을 수 없습니다.
용량이 아주 작은 통신 패킷이라면 한번에 받기도 하겠지만 지정된 버퍼보다 큰 사이즈 또는 네트워크 상태에 따라 정해지지 않은 크기를 주고받기때문에
InternetQueryDataAvailable API를 통해 현재 얼마만큼의 데이터를 읽을 수 있는지 확인하도록 합니다.
*/
DWORD read_available = 0;
if (FALSE == InternetQueryDataAvailable(
open_url_handle,
&read_available,
0,
0
)) {
result = false;
__debug_printf_trace("InternetQueryDataAvailable failed.");
break;
}
// resize buffer.
if (read_available > max_buffer_size) {
read_available = max_buffer_size;
}
//
// internet read file.
//
if (FALSE == InternetReadFile(
open_url_handle,
buffer,
read_available,
&number_of_bytes_read
)) {
result = false;
__debug_printf_trace("InternetReadFile failed.");
break;
}
if (0 < number_of_bytes_read) {
//
// write file.
//
save_file.Write(buffer, number_of_bytes_read);
memset(buffer, 0x00, number_of_bytes_read);
}
} while (0 != number_of_bytes_read);
save_file.Close();
}
InternetCloseHandle(open_url_handle);
} while (false);
return result;
}

 

코드가 복잡하지 않으니 하나하나 따라가 보겠습니다.

 

  • initialize() - 생성자에서 호출.
    • InternetOpen API를 이용하여 HINTERNET Handle을 초기화합니다. Agent값은 아무런 값이나 입력해도 되며, HTTP 프로토콜에서는 사용자 Agent로 사용되기도 합니다.

 

  • InternetCloseHandle() - 소멸자에서 호출
    • 생성한 Handle은 반드시 소멸해주어야 하기 때문에 잊지 않도록 소멸자에 등록합니다.

 

  • download()
    • 넘어온 parameter는 verify check를 해주어야겠죠? 필수 입력값이기 때문에 셋 중에 하나라도 미입력시 정상적으로 파일을 다운로드하지 못하거나 저장하지 못할 수 있습니다.

    • InternetOpenUrl API를 이용하여 실제 접속이 시작됩니다. 캐시가 아닌 원본 파일을 항상 다운로드하기 위해 Flag는 INTERNET_FLAG_RELOAD를 주었습니다. 다른 Flag를 설정하고 싶은 경우 여기(MSDN)를 참조하시면 됩니다.

    • 해당 URL에 404, 500 등 http error가 넘어오더라도 정상적인 URL 정보이기 때문에 InternetOpenUrl의 HINTERNET Handle이 넘어오게 됩니다. 그렇기 때문에 NULL 또는 INVALID_HANDLE 정보로는 에러 여부를 확인할 수 없으므로 HttpQueryInfo API를 이용하여 HTTP_QUERY_STATUS_CODE를 확인합니다. HTTP_STATUS_OK가 넘어왔다면 접속이 성공한 것이니 이제 파일로 저장하기만 하면 됩니다.

    • 여기가 가장 중요한 부분인데요 네트워크 통신이라는 게 패킷단위로 왔다 갔다 하다 보니 한 번의 통신으로 파일을 받을 수 없습니다. 용량이 아주 적은 통신 패킷이라면 한 번에 받기도 하겠지만 지정된 버퍼보다 큰 사이즈 또는 네트워크 상태에 따라 정해지지 않은 크기를 주고받기 때문에 InternetQueryDataAvailable API를 통해 현재 얼마만큼의 데이터를 읽을 수 있는지 확인하도록 합니다.

    • 마지막으로 다운로드 받은 파일을 로컬 파일로 저장하도록 합니다.

 

 

use sample

사용법도 간단합니다. 클래스 변수를 선언한 후 url, save path, file name을 지정하기만 하면 끝!

file_donwloader downloader;
if (false == downloader.download(
url.GetString(),
save_path.GetString(),
file_name.GetString()
)) {
CString message;
message.Format(L"파일 다운로드 실패!");
MessageBox(message.GetString());
break;
}

 

 

반응형
이 글은 (새창열림) 본 저작자 표시, 비영리, 변경 금지 규칙 하에 배포할 수 있습니다. 자세한 내용은 Creative Commons 라이선스를 확인하세요.
본 저작자 표시
비영리
변경 금지

'⌨ DEVELOPMENT > C++' 카테고리의 다른 글

[C/C++] clock vs gettimeofday Linux와 Windows의 코드 실행 시간 측정 방법  (6) 2020.05.03
[Solved] Resolver error: The VS Code Server failed to start  (3) 2019.12.14
[C/C++] Get Process Name by Process Id 3가지 방법  (0) 2019.12.12
[MFC] Dialog based - class name 지정하는 간단한 방법  (3) 2019.09.08
[C++] Boost ASIO를 이용한 안전한 TCP/IP 비동기 소켓 서버 예제  (5) 2019.08.07
[MFC] Dialog Load GIF image  (5) 2019.07.28
[C++] How to use GDIPlus Library in c++  (0) 2019.07.28
[MFC] 다이얼로그의 윈도우 소멸자 호출 순서  (1) 2019.07.28

댓글

댓글을 사용할 수 없습니다.

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [C/C++] clock vs gettimeofday Linux와 Windows의 코드 실행 시간 측정 방법

    [C/C++] clock vs gettimeofday Linux와 Windows의 코드 실행 시간 측정 방법

    2020.05.03
  • [Solved] Resolver error: The VS Code Server failed to start

    [Solved] Resolver error: The VS Code Server failed to start

    2019.12.14
  • [C/C++] Get Process Name by Process Id 3가지 방법

    [C/C++] Get Process Name by Process Id 3가지 방법

    2019.12.12
  • [MFC] Dialog based - class name 지정하는 간단한 방법

    [MFC] Dialog based - class name 지정하는 간단한 방법

    2019.09.08
다른 글 더 둘러보기

정보

웬디의 기묘한 이야기 블로그의 첫 페이지로 이동

웬디의 기묘한 이야기

  • 웬디의 기묘한 이야기의 첫 페이지로 이동

검색

메뉴

  • 홈
  • 태그
  • 방명록
  • 이야기

카테고리

  • 분류 전체보기 (204)
    • MY STORY (2)
    • 📸 WALKING WITH YOU (85)
      • 아이슬란드 신혼여행 이야기 (14)
      • 대한민국 구석구석 (62)
      • CONTAX N1 + T* 28-80mm (4)
      • SAMSUNG NX3000 (1)
      • 어느 멋진 날 (4)
    • ⌨ DEVELOPMENT (80)
      • BOOK:Review (1)
      • AI (13)
      • C++ (26)
      • Python (10)
      • WIndows Hooking (9)
      • Windows Kernel (3)
      • Design Pattern (3)
      • Debugging (9)
      • Tools (0)
      • Project (1)
      • Android (1)
      • 상업용 무료폰트 (4)
    • OS (4)
      • News (0)
      • Windows 일반 (4)
    • 모바일 (2)
      • 모바일 게임 (2)
    • 멘사 퍼즐 (9)
    • 생활 꿀 TIP (7)
      • 건강 (3)
      • 일상 (2)
    • 물생활 (8)
      • 골든볼 라미네지 롱핀 (8)
    • IT 기기 (2)
    • BLOG (4)
      • TISTORY BLOG TIP (3)

인기 글

공지사항

태그

  • 해외여행
  • windbg
  • c++
  • c
  • 신혼여행
  • 아이슬란드
  • AI
  • 카페

나의 외부 링크

  • kernel undocument api
  • 지구 관찰자의 일기
  • 지구와 지구곰

정보

WENDYS의 웬디의 기묘한 이야기

웬디의 기묘한 이야기

WENDYS

블로그 구독하기

  • 구독하기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. © WENDYS. Designed by Fraccino.

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.