1. 程式人生 > >c++實現檔案傳輸之二:功能實現

c++實現檔案傳輸之二:功能實現

在上一篇中,我們以經介紹了程式的流程和框架,在本篇將詳細討論各個功能的實現主要包括


1.獲取磁碟資訊
2.獲取目錄資訊
3.獲取檔案資訊
4.執行指定檔案
5.刪除指定檔案
6.刪除指定目錄
7.建立指定目錄
8.上傳下載檔案
9.獲取遠端檔案圖示


獲取磁碟資訊

磁碟資訊可以用API GetDriveType來實現,它以路徑名作為引數(如C:/)返回磁碟型別,其例項程式碼如下

DWORD GetDriverProc(COMMAND command,SOCKET client)
{
for(char i='A';i<='Z';i++)
{
char x[20]={i,':'};
UINT Type=GetDriveType(x);
if(Type==DRIVE_FIXED||Type==DRIVE_REMOVABLE||Type==DRIVE_CDROM)
{
/*返回處理結果...*/
}
}
return 0;
}

GetDriveType可能返回的結果如下

#define DRIVE_UNKNOWN    0 // 無效路徑名
#define DRIVE_NO_ROOT_DIR   1 // 無效路經,如無法找到的卷標
#define DRIVE_REMOVABLE 2 // 可移動驅動器
#define DRIVE_FIXED        3 // 固定的驅動器
#define DRIVE_REMOTE    4 // 網路驅動器
#define DRIVE_CDROM        5 // CD-ROM
#define DRIVE_RAMDISK    6 // 隨機存取(RAM)磁碟

在上面的例項程式碼中我們只取,硬碟,光碟機和移動磁碟




獲取目錄資訊




這裡只要列舉使用者指定的目錄就可以了,其例項程式碼如下:

DWORD GetDirInfoProc(COMMAND command,SOCKET client)
{
/*command為要列舉的路徑如(C:/)client為返回結果的SOCKET控制代碼*/

FILEINFO fi;
memset((char*)&fi,0,sizeof(fi));

strcat((char*)command.lparam,"*.*");//列舉所有檔案

CFileFind file;
BOOL bContinue = file.FindFile((char*)command.lparam);

while(bContinue)
{
memset((char*)&fi,0,sizeof(fi));

bContinue = file.FindNextFile();
if(file.IsDirectory()) //為目錄
{
fi.IsDir=true;
}
strcpy(fi.FileName,file.GetFileName().LockBuffer()); //儲存檔名稱

if(send(client,(char*)&fi,sizeof(cmd),0)==SOCKET_ERROR)
{
cout << "Send Dir is Error/n";
}
}
return 0;
}



獲取檔案資訊


以下例項程式碼用來獲取 檔案的名稱,路徑,時間,屬性等資訊

DWORD FileInfoProc (COMMAND command,SOCKET client)
{
/*command為要檢視的檔案如(C:/TEST.EXE)client為返回結果的SOCKET控制代碼*/
FILEINFO fi;
HANDLE hFile;
WIN32_FIND_DATA WFD;
memset((char*)&WFD,0,sizeof(WFD));
if((hFile=FindFirstFile((char*)command.lparam,&WFD))==INVALID_HANDLE_VALUE) //檢視檔案屬性
{
fi.Error=true;
return 0;
}
//得到檔案的相關資訊
SHGetFileInfo(WFD.cFileName,
FILE_ATTRIBUTE_NORMAL,
&shfi, sizeof(shfi),
SHGFI_ICON|SHGFI_USEFILEATTRIBUTES|SHGFI_TYPENAME );
strcpy(fi.FileName,(char*)command.lparam); //檔案路徑
FileLen=(WFD.nFileSizeHigh*MAXDWORD+WFD.nFileSizeLow)/1024; //檔案長度
fi.FileLen=FileLen;
//轉化格林時間到本地時間
FileTimeToLocalFileTime(&WFD.ftLastWriteTime,&localtime);
FileTimeToSystemTime(&localtime,&systime);
//檔案修改時間
sprintf(stime,"%4d-%02d-%02d %02d:%02d:%02d",
systime.wYear,systime.wMonth,systime.wDay,systime.wHour,
systime.wMinute,systime.wSecond);
if(GetFileAttributes((char*)command.lparam)&FILE_ATTRIBUTE_HIDDEN)
{
/*隱藏檔案...*/
}else
if(GetFileAttributes((char*)command.lparam)&FILE_ATTRIBUTE_READONLY)
{
/*只讀檔案...*/
}
send(client,(char*)&fi,sizeof(fi),0);
FindClose(hFile);
return 0;
}




執行指定檔案

執行檔案 有以下幾種方法 1.WinExec 2.ShellExecute 3.CreateProcess

這裡使用的是ShellExecute其例項程式碼如下

DWORD ExecFileProc (COMMAND command,SOCKET client)
{
/*command為要執行的檔案路徑如(C:/TEST.EXE)client為返回結果的SOCKET控制代碼*/

COMMAND     cmd;
memset((char*)&cmd,0,sizeof(cmd));
cmd.ID=ExecFile;

if(ShellExecute(NULL,"open",(char*)command.lparam,NULL,NULL,SW_HIDE)<(HINSTANCE)32)
{
strcpy((char*)cmd.lparam,"檔案執行失敗!");
send(client,(char*)&cmd,sizeof(cmd),0);
}
else
{
strcpy((char*)cmd.lparam,"檔案執行成功!");
send(client,(char*)&cmd,sizeof(cmd),0);
}

return 0;
}

API函式ShellExecute原形為:

HINSTANCE ShellExecute(    
HWND hwnd,             //視窗控制代碼
LPCTSTR lpOperation, //操作型別
LPCTSTR lpFile,          //檔案指標
LPCTSTR lpParameters,    //檔案引數
LPCTSTR lpDirectory, //預設目錄
INT nShowCmd          //顯示方式
);    

這是一個相當有意思的函式,在呼叫此函式時只須指定要執行的檔名,而不必管用什麼程式去開啟
或執行檔案,WINDOWS會自動根據要開啟或執行的檔案去判斷該如何執行檔案或用什麼程式去開啟檔案,如果
要求不高的話比CreateProcess要好用的多,如果想做出像NCPH和灰鴿子那樣帶引數執行的話,其實也不難
只要指定lpParameters為執行引數就可了


刪除指定檔案

DWORD DelFileProc (COMMAND command,SOCKET client)
{
/*command為要刪除的檔案路徑如(C:/TEST.EXE)client為返回結果的SOCKET控制代碼*/
COMMAND     cmd;
memset((char*)&cmd,0,sizeof(cmd));
cmd.ID=DelFile;
SetFileAttributes((char*)command.lparam,FILE_ATTRIBUTE_NORMAL); //去掉檔案的系統和隱藏屬性
if(DeleteFile((char*)command.lparam)==0)
{
strcpy((char*)cmd.lparam,"檔案刪除失敗!");
send(client,(char*)&cmd,sizeof(cmd),0);
}
else
{
strcpy((char*)cmd.lparam,"檔案刪除成功!");
send(client,(char*)&cmd,sizeof(cmd),0);
}
return 0;
}

需要注意的是在 DeleteFile前應該去檔案的系統和隱藏屬性,否則會刪除失敗


刪除目錄

可以用RemoveDirectory函式刪除目錄,但是RemoveDirectory有個缺點就是隻能刪除為空的的目錄,對於不為空
的目錄就無能為力了,想要刪除不無空的目錄可以使用下面的例項程式碼

BOOL DeleteDirectory(char *DirName)
{
CFileFind tempFind;
char tempFileFind[200];
sprintf(tempFileFind,"%s*.*",DirName);
BOOL IsFinded=(BOOL)tempFind.FindFile(tempFileFind);
while(IsFinded)
{
   IsFinded=(BOOL)tempFind.FindNextFile();
   if(!tempFind.IsDots())
   {
      char foundFileName[200];
      strcpy(foundFileName,tempFind.GetFileName().GetBuffer(200));
      if(tempFind.IsDirectory())
      {
         char tempDir[200];
         sprintf(tempDir,"%s//%s",DirName,foundFileName);
         DeleteDirectory(tempDir);
      }
      else
      {
         char tempFileName[200];
         sprintf(tempFileName,"%s//%s",DirName,foundFileName);
SetFileAttributes(tempFileName,FILE_ATTRIBUTE_NORMAL); //去掉檔案的系統和隱藏屬性
         DeleteFile(tempFileName);
cout <<"now delete "<<tempFileName<<"/n";
      }
   }
}
tempFind.Close();
if(!RemoveDirectory(DirName))
{
   return FALSE;
}
return TRUE;
}

這個函式的程式碼可以參照上面 列舉目錄的程式碼來看,它的原理就是列舉目錄下的所有檔案並刪除,最後刪除
指定目錄,成功返回TRUE失敗則返回FALSE,這段程式碼可以直使用,但要小心使用,因為我在傳引數時的失誤
結果把整個D盤差點清空了..........




建立目錄

例項程式碼如下:

DWORD CreateDirProc (COMMAND command,SOCKET client)
{
/*command為要建立目錄的路徑如(C:/)client為返回結果的SOCKET控制代碼*/
COMMAND     cmd;
memset((char*)&cmd,0,sizeof(cmd));
cmd.ID=CreateDir;
if(::CreateDirectory((char*)command.lparam,NULL))
{
strcpy((char*)cmd.lparam,"建立目錄成功!");
send(client,(char*)&cmd,sizeof(cmd),0);
}
else
{
strcpy((char*)cmd.lparam,"建立目錄失敗!可能有重名檔案或資料夾");
send(client,(char*)&cmd,sizeof(cmd),0);
}
return 0;
}


在建立目錄時應該注意幾點,首先創始目錄的上層目錄必須是存在的,比如想建立C:/DIR1/DIR2目錄,要求
DIR1是必須存在,用CreateDirectory並不能建立多級目錄.再者不可以存在和要建立目錄同名的目錄和檔案
因為在磁碟上目錄和檔案的存放格式是相同的,惟一不同的是 目錄的屬性與檔案屬性不同
(FILE_ATTRIBUTE_DIRECTORY屬性),所在即使有同名檔案也會創始失敗.




上傳下載檔案

上傳下載是是檔案管理的重點所在,在這裡按檔案的大小,分兩種情況討論檔案的傳輸方法

小檔案的傳輸相對比較簡單可按以下方法進行

1.首先發送檔案長度和名稱
2.跟據檔案長度建立緩衝區
3.讀取整個檔案到緩衝區
4.傳送緩衝區裡的內容

其實現程式碼如下:

CFile file;
FILEINFO fileinfo;
if(file.Open(path,CFile::modeRead|CFile::typeBinary))
{
fileinfo.FileLen=file.GetLength(); //檔案長度
strcpy(fileinfo.FileName,file.GetFileName()); //檔名稱
send(client,(char*)&fileinfo,sizeof(fileinfo),0); //傳送長度和名稱

char *date=new char[fileinfo.FileLen]; //分配和檔案長度相同的緩衝區
int nLeft=fileinfo.FileLen;
int idx=0;
file.Read(date,fileinfo.FileLen); //讀整個檔案到緩衝區
while(nLeft>0)
{
int ret=send(client,&date[idx],nLeft,0); //傳送檔案
if(ret==SOCKET_ERROR)
{
break;
}
nLeft-=ret;
idx+=ret;
}
file.Close();
delete[] date;
}
跟據上面的例項相信大家可以領悟到檔案傳輸的基本原理和方法,雖然很簡單但用它傳輸小檔案還是非常實用的


大檔案傳輸方法

用上面的方法傳輸小檔案還可以,但是大檔案呢?比如一個500M的電影.上面的方法就會力不從心了因為
按思路要建立一個跟檔案大小相同的緩衝區,顯然這是不太現實的,我們就得采用另種方法了,在這裡我們使用
分塊檔案傳輸,所謂分塊是指把大檔案分成若干小檔案,然後傳輸,比如設定每塊大小為64KB其思路如下


1.取得檔案長度和名稱
2.跟據長度/64KB計算檔案塊數
3.分配64KB緩衝區
4.讀檔案到緩衝區
5.傳送緩衝的資料
6.重複4,5兩步直到發完所有資料

其實現程式碼如下:

#define CHUNK_SIZE (64*1024) //分為64K塊傳輸

DWORD   GetFileProc (COMMAND command,SOCKET client)
{
/*command為要下載檔案的路徑如(C:/TEST.EXE)client為傳送檔案的SOCKET控制代碼*/

COMMAND     cmd;
FILEINFO fi;
memset((char*)&fi,0,sizeof(fi));
memset((char*)&cmd,0,sizeof(cmd));
cmd.ID=GetFile;

CFile file;
int nChunkCount=0; //檔案塊數

if(file.Open((char*)command.lparam,CFile::modeRead|CFile::typeBinary))//開啟檔案
{
int FileLen=file.GetLength(); //取檔案長度
fi.FileLen=file.GetLength();
strcpy((char*)fi.FileName,file.GetFileName()); //取檔名稱
memcpy((char*)&cmd.lparam,(char*)&fi,sizeof(fi));
send(client,(char*)&cmd,sizeof(cmd),0); //傳送檔名稱和長度

nChunkCount=FileLen/CHUNK_SIZE; //檔案塊數
if(FileLen%nChunkCount!=0)
nChunkCount++;
char *date=new char[CHUNK_SIZE]; //建立資料緩衝區
for(int i=0;i<nChunkCount;i++) //分為nChunkCount塊傳送
{
int nLeft;
if(i+1==nChunkCount) //最後一塊
nLeft=FileLen-CHUNK_SIZE*(nChunkCount-1);
else
nLeft=CHUNK_SIZE;
int idx=0;
file.Read(date,CHUNK_SIZE); //讀取檔案
while(nLeft>0)
{
int ret=send(client,&date[idx],nLeft,0);//傳送檔案
if(ret==SOCKET_ERROR)
{
break;
}
nLeft-=ret;
idx+=ret;
}
}
file.Close();
delete[] date;
}
return 0;
}
這樣檔案傳輸部分就完成了,止於客戶端的實現於上面程式碼其本相同,只是由讀檔案變為寫檔案,詳細請參考原始碼


獲取遠端ICO檔案圖示

我們在檔案列表框中需要顯示檔案的圖示,但遠端檔案的ICO圖示是無法直接得到的
猛若RADMIN 黑洞者也沒有到(對於EXE檔案只顯示可執行程式圖示),當然了也不見的決對沒有......

我們可以通過如下變通方法得到:就是跟據檔案的副檔名,從本地登錄檔中查詢對應的程式圖示

不過這也有它的缺點對於EXE檔案它只能顯示一個可執行檔案的圖示,而且只能顯示註冊過的圖示比如,如果
本機裝有WINRAR那麼就可以識別.RAR的檔案圖示,否則就無法識別...

實現方法

CImageList m_ImageList;

m_ImageList.Create(32,32,ILC_COLOR32,10,30); //建立圖示
m_list.SetImageList(&m_ImageList,LVSIL_NORMAL); //與列表控制元件相關連


SHFILEINFO info;
memset((char*)&info,0,sizeof(info));

SHGetFileInfo(fi->FileName,0,&info,sizeof(&info),   SHGFI_ICON|SHGFI_USEFILEATTRIBUTES);//關鍵所在

int i = m_ImageList.Add(info.hIcon);
m_list.InsertItem(i,fi->FileName,i);

原來我試圖在Server端通過上面的程式碼把info.hIcon控制代碼儲存下來,然後放到Client,在單臺電腦上很好使,但
Server在另一臺電腦上時就玩完了,因為info.hIcon裡儲存的控制代碼是個索引而每臺機器上的索引是不相同的所以
直接導致的結果就是:什麼也顯示不出來.....


到這裡程式的主要功能實現就介紹完了,下一篇將詳細介紹斷點續傳和多執行緒傳輸的實現!