スレッドを作り、HTTPでファイルをダウンロードする

最近はネット上にさまざまな情報やファイルがある。ブロードバンドの普及により通信速度も向上したこともあり、自分のプログラムからネット上の情報を参照したい場面がよくある。
ここではATL環境でマルチスレッドを利用してHTTPによりファイルをダウンロード/保存するクラスを作成した。

ここでは簡単のためスレッド生成数は1つのみで、キューにURLリストをためておき順次ダウンロードしている。

スレッドの生成にはCDnpThreadImpl、キューはCDnpQueを利用した。




■使用イメージ
	CDnpDownloadThread2	_cThread;

	//スレッドの生成とダウンロード開始
	{
		CAtlString	strURL;
		CAtlString	strFile;

		strURL = _T("http://www.usefullcode.net/atom.xml");
		strFile = _T("_down.xml");
		_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);

		strURL = _T("http://www.usefullcode.net/index.html");
		strFile = _T("_down.html");
		_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);
	}

	//メッセージハンドラ設定
	BEGIN_MSG_MAP(CMainDlg)
		MESSAGE_HANDLER(WM_DNP_DOWNLOAD_COMPLETE, OnDnpDownloadComplete)
	END_MSG_MAP()


	//ダウンロード結果の受信
	LRESULT OnDnpDownloadComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bool	ret = lParam ? true : false;

		ATLTRACE("■ダウンロード%s\n",(ret ? _T("成功") : _T("失敗")));

		return	0;
	}


■「DnpDownloadThread2.h」の内容
#pragma	once


#include "wininet.h" 
#pragma comment(lib,"wininet.lib")

#include "atlstr.h"
#include "atlfile.h"

#include "DnpThreadImpl.h"
#include "DnpQue.h"

#define	WM_DNP_DOWNLOAD_COMPLETE	(WM_APP + 66)



///
///\brief
///	HTTPを利用したダウンロードスレッド
///
///
///
/** \code
	CDnpDownloadThread2	_cThread;

	//スレッドの生成とダウンロード開始
	{
		CAtlString	strURL;
		CAtlString	strFile;

		strURL = _T("http://www.usefullcode.net/atom.xml");
		strFile = _T("_down.xml");
		_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);

		strURL = _T("http://www.usefullcode.net/index.html");
		strFile = _T("_down.html");
		_cThread.StartDownload(strURL,strFile,m_hWnd,NULL);
	}

	//メッセージハンドラ設定
	BEGIN_MSG_MAP(CMainDlg)
		MESSAGE_HANDLER(WM_DNP_DOWNLOAD_COMPLETE, OnDnpDownloadComplete)
	END_MSG_MAP()


	//ダウンロード結果の受信
	LRESULT OnDnpDownloadComplete(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bool	ret = lParam ? true : false;

		ATLTRACE("■ダウンロード%s\n",(ret ? _T("成功") : _T("失敗")));

		return	0;
	}
\endcode
*/
///
class CDnpDownloadThread2	: protected CDnpThreadImpl
{

	HINTERNET	_hInternet;			//!< 接続用
	HINTERNET	_hConnect;			//!< 接続用

	HANDLE		_hEvent;			//!< ダウンロード情報追加検出イベント用


	///
	///\brief
	///	ダウンロード情報格納用
	///
	class	CItem
	{
	public:
		CAtlString	strURL;			//!< ダウンロードURL
		CAtlString	strFile;		//!< 保存先ファイル名
		HWND		hWnd;			//!< ダウンロード結果通知先
		WPARAM		wParam;			//!< 結果通知時のユーザー定義パラメーター

		CItem()
		{
			wParam	= NULL;
			hWnd	= NULL;
		}

		CItem(LPCTSTR pszURL,LPCTSTR pszFile,HWND hUserWnd,WPARAM wUserParam)
		{
			strURL	= pszURL;
			strFile	= pszFile;
			hWnd	= hUserWnd;
			wParam	= wUserParam;
		}
	};

	//
	//ダウンロード情報格納用データとそのクリティカルセクション
	//
	CDnpCriticalSection	_sec_queItems;		//!< put_queItems() get_queItems() get_queItemsSize() 以外からはアクセスしないこと
	CDnpQue<CItem>		_queItems;			//!< put_queItems() get_queItems() get_queItemsSize()  以外からはアクセスしないこと

protected:

	///
	///\brief
	///	ダウンロード情報の追加
	///
	///\param[in]	cItem
	///ダウンロード情報
	///
	///\retval	true	追加成功
	///\retval	false	追加失敗
	///
	bool	put_queItems(const CItem& cItem)
	{
		HRESULT	hr;

		hr = _sec_queItems.Lock();
		if(FAILED(hr))
			return	false;

		_queItems.AddData(cItem);
		_sec_queItems.Unlock();

		return	true;
	}


	///
	///\brief
	///	ダウンロード情報の取得
	///
	///\param[out]	cItem
	///ダウンロード情報
	///
	///\retval	true	取得成功
	///\retval	false	取得失敗
	///
	bool	get_queItems(CItem& cItem)
	{
		bool	ret;
		HRESULT	hr;

		hr = _sec_queItems.Lock();
		if(FAILED(hr))
			return	false;

		ret = _queItems.GetData(cItem);
		_sec_queItems.Unlock();

		return	ret;
	}



	///
	///\brief
	///	まだ残っているダウンロード情報数
	///
	///\param[out]	nSize
	///ダウンロード情報数
	///
	///\retval	true	取得成功
	///\retval	false	取得失敗
	///
	bool	get_queItemsSize(size_t& nSize)
	{
		HRESULT	hr;

		hr = _sec_queItems.Lock();
		if(FAILED(hr))
		{
			nSize = 0;
			return	false;
		}

		nSize = _queItems.GetSize();
		_sec_queItems.Unlock();

		return	true;
	}




public:

	CDnpDownloadThread2()
	{
		_hEvent		= NULL;
		_hInternet	= NULL;
		_hConnect	= NULL;
	}

	virtual	~CDnpDownloadThread2()
	{
		////スレッドが破棄されていなければASSERT
		//ATLASSERT(_dwThreadID == 0 && _hThread == 0);

		DestroyThread(2000);			//スレッド終了を最大2秒待つ
	}




	///
	///\brief
	///	ダウンロード情報の追加およびダウンロード開始
	///
	///ほかにダウンロード中のファイルがある場合はそのダウンロードが終わるまで
	///ダウンロードは開始しない
	///
	///\param[in]	pszURL
	///ダウンロードしたいURL
	///
	///\param[in]	pszFile
	///保存先ファイル
	///
	///\param[in]	hWnd
	///結果の通知先HWND
	///
	///\param[in]	wParam
	///結果通知時のユーザー定義パラメーター
	///
	///\retval	true	成功(情報の追加成功であって、ダウンロード結果ではない)
	///\retval	false	失敗
	///
	bool	StartDownload(LPCTSTR pszURL,LPCTSTR pszFile,HWND hWnd,WPARAM wParam)
	{
		bool	ret;

		ret = put_queItems(CItem(pszURL,pszFile,hWnd,wParam));
		if(ret == false)
			return	false;

		if(_hEvent == NULL)
			_hEvent = ::CreateEvent(NULL,FALSE,TRUE,_T("CDnpDownloadThread2"));

		::SetEvent(_hEvent);

		if(IsThreadRunning())
			return	true;

		return	CreateThread(NULL) ? true : false;
	}



	///
	///\brief
	///	ダウンロードの中断
	///
	bool	AbortDownload(void)
	{
		return	AbortThread();
	}



protected:

	///
	///\brief
	///	スレッドメイン処理
	///
	/// CDnpThreadImpl::ThradMain_() から呼ばれる
	///ユーザーは呼び出さないこと
	///
	///\param	pParam
	///	CreateThread() で渡したユーザー定義のパラメータ
	///
	virtual	DWORD	ThreadMain(void* pParam)
	{
		bool	ret;
		size_t	nSize;
		HANDLE	hEvent;

		hEvent = ::OpenEvent(EVENT_ALL_ACCESS,FALSE,_T("CDnpDownloadThread2"));
	
		while(1)
		{
			//500ミリ秒ごともしくはイベントセット時に処理する
			::WaitForSingleObject(hEvent,500);

			// IsAbort() でスレッド中断を検出して関数から抜ける
			if(IsAbort())
				break;

			ret = get_queItemsSize(nSize);
			if(ret && nSize == 0)
			{
				::ResetEvent(hEvent);
				continue;
			}


			{
				CItem		cItem;
				bool		ret;
				HANDLE		hFile;
				CAtlFile	cFile;

				ret = get_queItems(cItem);
				if(ret == false)
					continue;

				ret = false;
				cFile.Create(cItem.strFile,GENERIC_WRITE,0,CREATE_ALWAYS);
				hFile = cFile.Detach();
				if(hFile)
				{
					ret = Download(cItem.strURL,hFile);
					::CloseHandle(hFile);
				}
				if(cItem.hWnd && ::IsWindow(cItem.hWnd))
					::PostMessage(cItem.hWnd,WM_DNP_DOWNLOAD_COMPLETE,cItem.wParam,(LPARAM)ret);
			}
		}
		::CloseHandle(hEvent);

		return	0;
	}





	///
	///\brief
	///	スレッド中断処理開始用
	///
	///スレッドを中断したいときに呼び出す
	///
	///\retval	true	設定成功
	///\retval	false	失敗
	///
	bool	AbortThread(void)
	{
		if(_hEvent)
			::SetEvent(_hEvent);

		if(_hConnect)
			::InternetCloseHandle(_hConnect);
		if(_hInternet)
			::InternetCloseHandle(_hInternet);
		_hConnect = NULL;
		_hInternet = NULL;

		if(_hEvent)
			::CloseHandle(_hEvent);
		_hEvent = NULL;

		return	CDnpThreadImpl::AbortThread();
	}






	///
	///\brief
	///		HTTPを利用したダウンロード関数
	///
	///ダウンローが終了(もしくは失敗や中断)するまでreturnしない
	///
	///\param[in]	pszURL
	///ダウンロードするURL
	///
	///\param	hFile
	///保存先ファイルのハンドル
	///
	///\param[in]	dwBuffSize
	///受信バッファーサイズ
	///
	///\retval	true	ダウンロードは正常に終了
	///\retval	false	失敗
	///
	///
	virtual	bool	Download(LPCTSTR pszURL,HANDLE hFile,DWORD dwBuffSize=1024)
	{
		TCHAR		pszHeader[] = _T("Accept: */*");
		BOOL		ret;
		DWORD		dwReadSize;
		DWORD		dwWrittenSize;
		BYTE*		pcbBuff;

		pcbBuff = new BYTE[dwBuffSize];
		if(pcbBuff == NULL || IsAbort())
			return	false;

		_hInternet = ::InternetOpen(NULL,INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);
		if(_hInternet == NULL || IsAbort())
		{
			delete	pcbBuff;
			return	false;
		}

		_hConnect = ::InternetOpenUrl(_hInternet,pszURL,pszHeader,lstrlen(pszHeader),INTERNET_FLAG_DONT_CACHE,0);
		if(_hConnect == NULL || IsAbort())
		{
			::InternetCloseHandle(_hInternet);
			_hInternet = NULL;
			delete	pcbBuff;
			return	false;
		}

		while(1)
		{
			//if(IsAbort())
			//	break;

			::Sleep(0);
			ret = ::InternetReadFile(_hConnect,pcbBuff,dwBuffSize,&dwReadSize);
			if(ret == FALSE)
				break;

			if(dwReadSize == 0 || IsAbort())
				break;

			ret = ::WriteFile(hFile,pcbBuff,dwReadSize,&dwWrittenSize,NULL);
			if(ret == FALSE)
				break;
		}

		if(ret)
			ret = ::FlushFileBuffers(hFile);
		::InternetCloseHandle(_hConnect);
		::InternetCloseHandle(_hInternet);
		_hConnect = NULL;
		_hInternet = NULL;

		delete	pcbBuff;

		return	(ret == FALSE || IsAbort()) ? false : true;
	}

};


プロジェクトファイルをダウンロード


カテゴリー「ネットワーク」 のエントリー