[C/C++] InternetReadFile Simple File Download.
복잡한 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를 통해 현재 얼마만큼의 데이터를 읽을 수 있는지 확인하도록 합니다.
- 마지막으로 다운로드 받은 파일을 로컬 파일로 저장하도록 합니다.
- 넘어온 parameter는 verify check를 해주어야겠죠? 필수 입력값이기 때문에 셋 중에 하나라도 미입력시 정상적으로 파일을 다운로드하지 못하거나 저장하지 못할 수 있습니다.
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; }
'⌨ 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 |
댓글
이 글 공유하기
다른 글
-
[C/C++] clock vs gettimeofday Linux와 Windows의 코드 실행 시간 측정 방법
[C/C++] clock vs gettimeofday Linux와 Windows의 코드 실행 시간 측정 방법
2020.05.03windows program을 주력으로 개발하다 보니 자연스럽게 사용하던 clock을 이용하여 시간을 측정했습니다. 그런데 linux에서도 똑같이 시간을 측정했는데 system() API를 사용한 부분이 예상 시간이랑 너무나도 차이가 있더라고요…. 확인해보니 리눅스에서의 시간 측정 방법이 잘못된 거였습니다. LINUX에서 실행 시간 측정 clock() CPU의 시간을 측정하는 API입니다. 즉, 프로세스가 CPU를 점유하지 않을 때는 시간이 측정되지 않게 되는데요, sleep(), system() API 등 CPU가 점유하지 않는 API를 사용한 시간은 측정이 되지 않습니다. gettimeofday() wall clock time이라고 하여 동작 시간을 측정하는 방식입니다. CPU의 동작 시간만을 확인… -
[Solved] Resolver error: The VS Code Server failed to start
[Solved] Resolver error: The VS Code Server failed to start
2019.12.14visual studio code insiders Remote-SSH ERROR visual studio code에서 remote-ssh를 통해 원격환경에서 개발을 하던 도중 다음과 같은 에러가 나오게되었습니다. 해당 문제는 remote-ssh도중 잘못된 데이터가 캐시 되었거나 visual studio code가 비정상 종료되었을 때 캐시를 제대로 정리하지 못해서 발생한것으로 판단됩니다. Remote-SSH ERROR OUTPUT MESSAGE [16:53:27.151] "install" terminal command done [16:53:27.151] Install terminal quit with output: 270fd8b8678f##32## [16:53:27.151] Received install… -
[C/C++] Get Process Name by Process Id 3가지 방법
[C/C++] Get Process Name by Process Id 3가지 방법
2019.12.12How to get the process name from process id in C++ 현재 실행 중인 프로세스 또는 외부 프로세스의 이름 정보를 얻어오는 방법들을 정리합니다. MSDN을 참고하여 정리하였으며, MS에서 권장하는 각각의 방식들이 존재하기 때문에 그에 맞게 개발하면 됩니다. 현재 실행중인 프로세스 이름 정보를 획득하는 방법 현재 프로세스 정보를 획득하기 위해서는 별다른 정보가 필요 없습니다. 아주 간단하게 GetModuleFileName API를 이용하는 방법입니다. https://docs.microsoft.com/ko-kr/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew GetModuleFileNameW functi… -
[MFC] Dialog based - class name 지정하는 간단한 방법
[MFC] Dialog based - class name 지정하는 간단한 방법
2019.09.08MFC Dialog - FindWIndow를 위한 Class Name 지정하는 방법 요즘에도 MFC를 개발하는 사람이 있는지 모르겠네요…. 이번에 MFC Project 중 FindWindow를 사용해야 하는데, Window Title을 바탕으로 하게 되면 오탐이 생길 수 있기 때문에 Class Name을 지정하여 정확한 윈도우를 찾아내기로 했습니다. 먼저 MFC Dialog based 로 프로젝트를 생성하게 되면 기본 Class Name은 #32770입니다. 그렇다면 Class Name을 어떻게 변경해야 할까요? 기본적으로 project.rc 파일이 생성되고, 그 안에 Dialog의 Properties를 보게 되면 Class Name이라는 항목이 존재합니다. 그런데 보면 알 수 있듯이 비활성화가 되어있…
댓글을 사용할 수 없습니다.