利用WinInet和多线程实现实时显示的下载进度条

来源:互联网 发布:java换ip刷页面访问量 编辑:程序博客网 时间:2024/06/11 23:08

利用WinInet和多线程实现下载进度实时显示

 

作者:阿珊境界

 

源代码下载

大家对Internet文件下载一定不陌生,如果不讲究下载细节,一个API函数URLDownloadToFile就能搞定。但如果你要下载的数据量较大,或你的软件需要在线升级,那么,还是给个进度条让用户看看,免得让人以为你的软件已经挂掉。要实现这些,首先用到的就是多线程技术,把下载线程和界面线程分开;其次,要把下载进度及时反馈给界面进度条,还要用到WinInet提供的接口。程序界面如图1

1

本例程用到4WinInet接口函数,分别是InternetGetConnectdStateInternetOpenInternetOpenUrlHttpQueryInfo,分别用以获取当前网络连接信息、打开网络连接、打开具体URL和查看连接信息。在工程中,我们写一个函数InternetGetFile对下载细节进行封装,代码如下:

int CDownloadProgressDlg::InternetGetFile (HWND hWnd,CString szUrl,CString szFileName)

{

      DWORD dwFlags;

      InternetGetConnectedState(&dwFlags, 0);    //获取当前网络连接信息

      CHAR strAgent[64];

      sprintf(strAgent, "Agent%ld", timeGetTime()); //timeGetTime用以获取系统启动后的时间

      HINTERNET hOpen;

      if(!(dwFlags & INTERNET_CONNECTION_PROXY))

             hOpen = InternetOpenA(strAgent, INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, NULL, NULL, 0);

      else

             hOpen = InternetOpenA(strAgent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);

      if(!hOpen)

      {

             AfxMessageBox("Internet连接错误!");

             return -1;

      }

 

      DWORD dwSize;

      //CHAR   szHead[] = "Accept: */*/r/n/r/n";

      CHAR szHead[] =

                    _T("Accept: */*/r/nAccept-Language: zh-cn/r/nAccept-Encoding: gzip, deflate/r/nUser-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; MyIE2; .NET CLR 1.1.4322)");

      VOID* szTemp[16384];

      HINTERNET  hConnect;

      CFile file;   

      if ( !(hConnect = InternetOpenUrlA ( hOpen, szUrl, szHead,

              lstrlenA (szHead), INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD, 0)))

      {

         AfxMessageBox("不能打开该URL!");

         return -1;

      }

 

      if  (file.Open(szFileName,CFile::modeWrite|CFile::modeCreate)==FALSE )

      {

         AfxMessageBox("不能打开本地的文件!");

        return -1;

      }

 

      DWORD dwByteToRead = 0;

      DWORD dwSizeOfRq = 4;

      DWORD dwBytes = 0;

 

   if (!HttpQueryInfo(hConnect, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,

                 (LPVOID)&dwByteToRead, &dwSizeOfRq, NULL))

      {

             dwByteToRead = 0;

      }

 

      DWORD start;   //记录时间

      DWORD end;

      DWORD time;

      int nPos;

      CString tempstring;

      time = 10;

      start = timeGetTime();

      do

      {

             if (!InternetReadFile (hConnect, szTemp, 16384,  &dwSize))

             {

                    AfxMessageBox("读文件出错!");

                    file.Close();

                    return -1;

             }

             if (dwSize==0)

                    break;

             else

                    file.Write(szTemp,dwSize);

             dwBytes+=dwSize;

             

             if(dwByteToRead)

             {

                    tempstring.Format("%d%%",(dwBytes*100)/dwByteToRead);

                    nPos=(dwBytes*100)/dwByteToRead;

//                 SetDlgItemText(IDC_PERCENT_TEXT,tempstring);

             }

             float fSpeed = 0;    //计算速度

             fSpeed = (float)dwBytes;

             fSpeed /= (float)((float)time/(float)1000);

             fSpeed /= (float)1024;

             //tempstring.Format("%4.2fKB/S",fSpeed);

             

             TRACE(tempstring);

             

//下面的代码我将作出解释,请看下文

SHOWINFO* pShowInfo=new SHOWINFO;

             pShowInfo->strSpeed.Format("%4.1f KB/",fSpeed);

             pShowInfo->nPos=nPos;

::PostMessage(hWnd,MSG_PROGRESS,0,(LPARAM)pShowInfo);

             

end = timeGetTime(); 

             time = end - start;

             if(time == 0)

                    time = 10;

      }while (TRUE);

 

      file.Close();

      InternetCloseHandle(hOpen);   //关闭句柄

      return 0;

}

这个函数的后半段代码我解释一下。SHOWINFO是一个自定义的结构体,用其把当前速度和进度传递给界面进度条。定义如下:

struct SHOWINFO

{

      CString strSpeed;

      int nPos;

};

传递信息给界面的方式是用PostMessage,这里,它的第二个参数就用SHOWINFO的对象了。注意使用时强制转化为LPARAMMSG_PROGRESS是个自定义消息,它的响应函数为OnStep,在其中处理进度条的显示。代码如下:

LRESULT CDownloadProgressDlg::OnStep(WPARAM wParam, LPARAM lParam)

{

      CString strSpeed=((SHOWINFO*)lParam)->strSpeed;

      int nPos=((SHOWINFO*)lParam)->nPos;

      delete (SHOWINFO*)lParam;

      

      CString strPercent;

      strPercent.Format("%d%%",nPos);

      SetDlgItemText(IDC_SPEED_TEXT,strSpeed);

      

      SetDlgItemText(IDC_PERCENT_TEXT,strPercent);

      m_progress.SetPos(nPos);

      if(nPos==100)

      {

             GetDlgItem(IDC_DOWNLOAD)->EnableWindow(TRUE);

             GetDlgItem(IDOK)->EnableWindow(TRUE);

 

             MessageBox("文件下载完成!","OK",MB_ICONINFORMATION);

      }

      return TRUE;

}

函数InternetGetFile在单独的线程中调用,起新线程的动作在“下载”按钮点击事件中进行,代码如下:

void CDownloadProgressDlg::OnDownload()

{

      // TODO: Add your control notification handler code here

      CString url,filename;

      GetDlgItemText(IDC_FILE_URL,url);

      GetDlgItemText(IDC_LOCAL_DIRECTORY,filename);

      if(url.IsEmpty())

      {

             AfxMessageBox("请输入Internet文件的URL!");

             return;

      }

      if(filename.IsEmpty())

      {

             AfxMessageBox("请输入需要保存的文件路径!");

             return;

      }

   //下面代码我要解释一下,见下文

      DOWNINFO* pDownInfo=new DOWNINFO;

      pDownInfo->hWnd=m_hWnd;

      pDownInfo->url=url;

      pDownInfo->filename=filename;

      //GetDlgItem(IDC_DOWNLOAD)->EnableWindow(FALSE);

      //GetDlgItem(IDOK)->EnableWindow(FALSE);

      AfxBeginThread(DownProc,(LPVOID)pDownInfo);

}

上面函数中的DOWNINFO也是一个自定义结构体,用以向新线程传递句柄、URL和文件名。定义如下:

struct DOWNINFO

{

      HWND hWnd;

      CString url;

      CString filename;

};

DownProc是个回调函数,它的函数结构和参数表系统已经为我们定义好。函数内容如下:

UINT CDownloadProgressDlg::DownProc(LPVOID pDownInfo)

{

      HWND hWnd=((DOWNINFO*)pDownInfo)->hWnd;

      CString url,filename;

      url=((DOWNINFO*)pDownInfo)->url;

      filename=((DOWNINFO*)pDownInfo)->filename;

      if(InternetGetFile(hWnd,url,filename)==0)

      {

             //AfxMessageBox("下载成功!");

             //g_nAbort=TRUE;

             return 1;

      }

      else

      {

             AfxMessageBox("下载失败!");

             return 0;

      }

}

   回顾本例程,我们的代码并没有用到计时器去显示进度,而是每下载一定字节数就向窗口发送自定义消息来让进度条显示进度,所以做了实时性和准确性。文中代码在VC6.0 + Win XP中调试通过,如有不妥之处,欢迎批评指正。

 
原创粉丝点击