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 的字符串为参数,接受这个文件的数据,并保存到指定的位置。变量
savespath和
host都是全局的,在程序的定义是: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。谢谢。
参考文件
[1] Madings,
http://www.vckbase.com/code/downcode.asp?id=2727 Feb 03, 2006.