VC and Winsocket Programming - Downloading File From a Website

Using VC Develop a Tool to Download File
1 、介绍和背景
昨天决定写一个程序“批量下载网页上相同格式的文件”,本以为用VC会很轻松搞定,但一用才知道,WinSocket编程几乎忘光了,弄了几乎整个下午加半个晚上都还徘徊在起点上,唉,又犯了眼高手低的错误。所以呢,我想应该把“批量下载网页上相同格式的文件”分成几个部分,或说是步骤,一步步来吧。现在就让我们来开发了一个可以从指定的服务器上下载指定文件的小程序。
2 、过程
2.1 、创建一个VC 工程
打开VS6.0,创建一个基于对话框的MFC工程,选中支持Winsocket的选项,工程名为SimpleDownload。
2.2 、设计界面
大致想了一下,这个程序应包括一下几个部分。
  • WEB 服务器名字 –〉从哪里下载文件
  • 文件URL –〉下载哪个文件
  • 文件保存位置 –〉保存到什么位置
  • 运行结果 –〉程序运行的状态
根据这个想法,打开创建的工程,选择Resource View,设计出程序的界面。最终结果如Figure 1所示。
Figure 1 Screen
 
关于各个组件的详细信息见Figure 2。Figure 2种编号一列请参照Figure 3。Figure 2中只列出了编号1-7。
组件
编号
名称
ID
Caption
相关变量
CEdit
1
Host
IDC_HOST
--
m_EditCrlHost
2
File URL
IDC_URL
--
m_EditCtrlURL
3
Save path
IDC_SAVEPATH
--
m_EditCtrlPath
CButton
4
Download
IDC_DOWNLOAD
&Download
--
5
Cancel
IDCANCEL
Cancel
--
CStatic
6
Status
IDC_STATIC_STATUS
Static
m_StaticCtrlStatus
CListBox
7
Result
IDC_LIST_RESULT
--
m_ListCtrlResult
 
 
 
 
 
Figure 2 Component Description
Figure 3 Component Layout
2.3、添加函数
下面给Download Button添加click处理函数 OnDownload 。代码如下。
 
void CSimpleDownloadDlg::OnDownload()
{
       CString strTemp;
       CString strTemp1;
       TCHAR szRelativeURL[1024];
      
       // initiates use of Ws2_32.dll and specify the version of Windows
       // Sockets required and retrieve details of the specific Windows
       // Sockets implementation
       WSADATA wsa;
       WSAStartup(MAKEWORD(2,2),&wsa);
      
       // Get the Host
       m_EditCtrlHost.GetWindowText(strTemp);
 
       if (strTemp == _T("")) {
              AfxMessageBox(_T("Please provide the Host"));
              return;
       }
       else strcpy(host, strTemp);
 
       // Get Save Path
       m_EditCtrlPath.GetWindowText(strTemp);
       strcpy(savespath, strTemp);
      
       // Get File URL
       m_EditCtrlURL.GetWindowText(strTemp);
       strTemp.TrimLeft(_T("/t "));
       strTemp.TrimRight(_T("/t ;"));
 
       if (strTemp == _T("")) {
              AfxMessageBox(_T("Please provide the URL"));
              return;
       }
      
       int start = 0;
       int end = 0;
       char *param;
       char filename[128];
       char statusbuf[128];
 
       do {
              // the file url input can include multiple relative file urls
              // and ';' is the delimiter.
             // for example, "/images/1.jpg;/images/2.jpg"
              end = strTemp.Find(';', start);
              if(end == -1)
                     end = strTemp.GetLength();
              // extract one url
              // strTemp1 will be like "/images/1.jpg" "/images/2.jpg"
              strTemp1 = strTemp.Mid(start, end - start);           
              start = end + 1;
             
              // copy the relative url
              lstrcpy(szRelativeURL, strTemp1);
 
              // get the real file name..
              // for "/images/1.jpg", the real file name is "1.jpg",
              // for "/images/2.jpg", the real file name is "2.jpg".
              for(int i = strlen(szRelativeURL); i > 0; i--){
                     if( szRelativeURL[i-1] == '/' )
                           break;
              }
              memset(filename, 0, 128);
              memcpy(filename, szRelativeURL+i, strlen(szRelativeURL)-i);
             
              // if the file has already existed
              if( isexist(filename) ) {       
                     sprintf(statusbuf, "%s already exists./r/n", szRelativeURL);
                     m_StaticCtrlStatus.SetWindowText(statusbuf);
                     m_ListCtrlResult.AddString(statusbuf);
                     continue;
              }
 
              // start the thread
              param = new char[strlen(szRelativeURL)+1];
              strcpy(param, szRelativeURL);
              _beginthreadex(0, 0, getandsave, (void*)param, 0, 0);
             
       } while(start < strTemp.GetLength());
 
       // clean up and release resource
       WSACleanup();
}
Figure 4 OnDownload Source Code
 
其中getandsave函数就像它的名字暗含的意思一样,以一个包含File URL 的字符串为参数,接受这个文件的数据,并保存到指定的位置。变量 savespathhost都是全局的,在程序的定义是:char savespath[128] char host[256] 。函数isexist(filename)判断该文件是否已经存在,Figure 4中涉及到的函数和变量见下面Figure 5 具体的代码。
unsigned int WINAPI getandsave(void* param){   
    char* url = (char*)param;
    // get the status hanlder
    CStatic * pStatus = (CStatic *)
        AfxGetApp()->GetMainWnd()->GetDlgItem(IDC_STATIC_STATUS);
    // get the result list handler
    CListBox * pResult = (CListBox *)
        AfxGetApp()->GetMainWnd()->GetDlgItem(IDC_LIST_RESULT);
       
    char statusbuf[128];
   
    if( strlen(url)<=0 ||strlen(url)>254){     
        return false;
    }
 
    // get the real file name
    char filename[128];
    for(int i=strlen(url);i>0;i--){
        if(url[i-1]=='/')
            break;
    }
    memset(filename,0,128);
    memcpy(filename,url+i,strlen(url)-i);
   
    // if the current running thread is more that 20,
    // wait for a moment...
    while( g_Threadnum > 20 )
        ::Sleep(20);
 
    // synchronizing access to g_Threadnum
    // prevents more than one thread from using g_Threadnum simultaneously.
    InterlockedIncrement(&g_Threadnum);
   
    struct sockaddr_in sin;
    // create socket
    SOCKET sClient = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   
    if(sClient==INVALID_SOCKET)
    {
        // printf("Socket Error !/r/n");
        sprintf(statusbuf, "Socket Error !/r/n");
        pStatus->SetWindowText(statusbuf);
        pResult->AddString(statusbuf);
        InterlockedDecrement(&g_Threadnum);
        return false;                                  
    }
    // get host by name
    hostent* pHostent = gethostbyname(host);
   
    if (pHostent == NULL) {
        // printf("Error: %u/n", WSAGetLastError());
        sprintf(statusbuf, "Error: %u/n", WSAGetLastError());
        pStatus->SetWindowText(statusbuf);
        pResult->AddString(statusbuf);
        return -1;
    }
   
    sin.sin_family = pHostent->h_addrtype;
    sin.sin_port = htons(80);
    memcpy (&sin.sin_addr.s_addr, pHostent->h_addr_list[0], pHostent->h_length);
    // sin.sin_addr.S_un.S_addr = inet_addr(inet_ntoa(sin.sin_addr));
   
    // connect to the server
    if( connect(sClient,(sockaddr*)&sin,sizeof(sin))==SOCKET_ERROR){
        // printf("Connect to [%s] error, errorcode=%d.",
        //     inet_ntoa(sin.sin_addr), GetLastError());
        sprintf(statusbuf, "Connect to [%s] error, errorcode=%d.",
            inet_ntoa(sin.sin_addr), GetLastError());
        pStatus->SetWindowText(statusbuf);
        pResult->AddString(statusbuf);
        InterlockedDecrement(&g_Threadnum);
        return false;
    }
   
    const int MLEN=192;
    char message[MLEN];
   
    // contruct the HTTP header
    strcpy(message,"GET ");
    if(url[0]=='/') {
        strcat(message,"http://");
        strcat(message, host);
    }
    strcat(message,url);
    strcat(message," HTTP/1.0/r/n");   
   
    send(sClient,message,strlen(message),0);
   
    strcpy(message,"accept: www/source; text/html; image/jpeg; */*/r/n");
    send(sClient,message,strlen(message),0);
   
    // There must be blank line
    strcpy(message,"/r/n");
 
    // send the message to server
    send(sClient,message,strlen(message),0);
   
    char rec[MLEN];
    // receive
    recvline(sClient,rec);
    if( strcmp(rec,"HTTP/1.1 200 OK")){
        // printf("Get file [%s] head error./r/n",url);
        sprintf(statusbuf, "getfile [%s] head error./r/n",url);
        pStatus->SetWindowText(statusbuf);
        pResult->AddString(statusbuf);
        InterlockedDecrement(&g_Threadnum);
        return false;
    }
   
    int mlen=0;
    while( recvline(sClient,rec) ){
        if( strlen(rec)<=0 )
            break;
        if(strstr(rec,"Content-Length:")==rec){
            char length[32];
            memset(length, 0, 32);
            memcpy(length, rec+15, strlen(rec)-15);
            mlen = atoi(length);
        }
    }
   
    // allocate memory for the file
    char* data = new char[mlen];
    int len=0;
   
    while(len<mlen){
        int add=::recv(sClient,data+len,mlen-len,0);
        len += add;
    }
    // save file
    savetodisk(filename,data,mlen);
   
    closesocket(sClient);
   
    // printf("savefile /"%s/" OK./r/n", url);
    sprintf(statusbuf, "savefile /"%s/" OK./r/n", url);
    pStatus->SetWindowText(statusbuf);
    pResult->AddString(statusbuf);
   
    InterlockedDecrement(&g_Threadnum);
   
    delete [] url;
    delete [] data;
    return 0;
}
 
int recvline(SOCKET s,char* c){
    int i=0;
    while(::recv(s,c+i,1,0)>0){
       if(c[i]=='/n')
            break;
        i++;
    }
   
    if( c[i]=='/n'){
        c[i-1]=0;
        return i-1;
    }
    return false;
}
 
bool savetodisk(char* fname,char* data, int len)
{
    // if the file name is > 32, extract the last 32 chars as new name.
    if(strlen(fname) > 32)
        strcpy(fname,fname+strlen(fname)-32);
    // fn store the full file name
    char fn[256];  
    strcpy(fn, savespath);
 
    if (savespath[strlen(savespath) - 1] != '/') {
        strcat(fn, "/");
    }
    strcat(fn, fname);
    // open the fn, if not find, create
    FILE* f=fopen(fn,"ab+");   
    if( !f )
        return false;
    // write data to the file
    fwrite(data,sizeof(char),len,f);   
    fclose(f); 
    return true;
}
 
bool isexist(char * fname)
{  
    // extract the last 32 chars only
    if(strlen(fname) > 32)
        strcpy(fname, fname+strlen(fname)-32); 
    char fn[256];
    strcpy(fn,savespath);
 
    if (savespath[strlen(savespath) - 1] != '/') {
        strcat(fn, "/");
    }
    strcat(fn,fname);
    FILE* f=fopen(fn,"r");
   
    if( f!=0) {
        fclose(f); 
        return true;
    }  
    return false;
}
Figure 5 More Source Code
2.4 运行
编译,运行该程序。结果如下。该Server的名称为hydhtc47918,我们一下子下载的文件都在images目录下,名称从main_01.jpg到main_05.jpg,下载后保存的位置为C:/Downloads。结果列表和状态都现在文件下载成功了。我们可以打开C:/Downloads,看看究竟下载成功了没有。Figure 7显示,确实成功了。
Figure 6 Result 1
Figure 7 Result 2
3、结论
到这里,这个简单的程序就完成了,写代码间参考了参考[1],作者的有些代码和函数写的很漂亮,我作了小许修改,用在了这个程序中,在此谢过了。有了这个程序作基础,“批量下载网页上相同格式的文件”就不难完成了。大致思路是得到包含下载链接的页面,分析其代码,解析得到相应格式的批量文件,比如说都是MP3,然后一个个下载,保存。有时间了,就把它实现了。
 
最后,在看这篇文章的过程中,如果你发现那里有错,或者确的哪里可以改进,或者其它任何问题,请联系 qianyanjiang@gmail.com。谢谢。
 
参考文件
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值