天天看點

VC中ftp協定實作多線程斷點續傳

     http://tech.163.com/school · 2005-09-19 16:34:25 · 來源: PConline

      ftp下載下傳的好處我在這裡就不多說了,許多工程會把ftp下載下傳作為一個重要的功能來實作。微軟提供的WinInet類可以利用下面這些函數:

  InternetOpen;   InternetConnect;   GetCurrentDirectory;   SetCurrentDirectory;   FtpGetFile;

  很容易實作ftp的下載下傳,網上關于這方面的文章也很多。但是要實作ftp的多線程下載下傳,利用這些函數就顯得有些牽強了。用socket根據ftp協定來開發将會變的十分靈活。下面我就逐漸的講解整個開發的過程:開發環境 BCB(元件模式),VC 環境下請自行稍作改動。看了這篇文章後對于BCB開發人員來說,不僅可以對 FlashGet 等軟體的開發原理有一定的了解,特别是在開發元件方面也有很大的指導作用,請耐心的将它看完。很簡單!!

  首先介紹一下部分ftp協定:

VC中ftp協定實作多線程斷點續傳

圖一 FTP服務示意圖

  使用者FTP和伺服器FTP之間要傳送檔案,需要有兩個連接配接:指令通道和資料連接配接,從名字上就可以看出指令通道是傳送指令的,資料通道是用于傳送檔案。伺服器與伺服器之間的資料傳送在此就不多作解釋。

主要用到的指令為:USER,PASS,TYPE,SIZE,REST,CWD,PWD,RETR,PASV,PORT,QUIT;

  USER:參數是标記使用者的Telnet串。使用者标記是通路伺服器必須的,此指令通常是控制連接配接後第一個發出的指令,有些主機還會要求密碼和帳戶。伺服器可以在任何時間接收新的USER指令以改變通路控制和(或)帳戶資訊。這可以重新開始登入過程,是以傳輸參數不變,在進行中的檔案傳輸在過去的通路控制參數下完成。

PASS:參數是标記使用者密碼的Telnet串。此指令緊跟USER指令,在某些站點它是完成通路控制不可缺少的一步。是以密碼是個重要的東西,是以不能顯示出來,伺服器方沒有辦法隐藏密碼,是以這一任務得由使用者FTP程序完成。

  TYPE:參數指定表示類型。有些類型需要第二個參數,第一個參數由單個Telnet字元定義,第二個參數是十進制整數指定位元組大小,參數間以 分隔。下面是格式:

VC中ftp協定實作多線程斷點續傳

圖二 TYPE參數示意圖

  預設表示類型是ASCII非列印字元,如果參數未改變,以後隻改變了第一個參數,則使用預設值。

  SIZE:參數從FTP伺服器上傳回指定檔案的大小。

  REST:參數域代表伺服器要重新開始的那一點,此指令并不傳送檔案,而是略過指定點後的資料,此指令後應該跟其它要求檔案傳輸的FTP指令。

  CWD:此指令使使用者可以在不同的目錄或資料集下工作而不用改變它的登入或帳戶資訊。傳輸參數也不變。參數一般是目錄名或與系統相關的檔案集合。

  PWD:改變目前的工作目錄。

  RETR:開始傳送指定的檔案。(從REST參數指定的偏移量開始傳送)

  PASV:此指令要求伺服器DTP在指定的資料端口偵聽,進入被動接收請求的狀态,參數是主機和端口位址。

  PORT:參數是要使用的資料連接配接端口,通常情況下對此不需要指令響應。如果使用此指令時,要發送32位的IP位址和16位的TCP端口号。上面的資訊以8位為一組,逗号間隔十進制傳輸。

  QUIT:登出。

各個參數的具體用法舉例如下:

USER sandy /r/n //使用者名為sandy登入 PASS sandy /r/n //密碼為sandy TYPE I /r/n SIZE sandy.txt /r/n //如果sandy.txt檔案存在,則傳回該檔案的大小 REST 100 /r/n //重新指定檔案傳送的偏移 CWD infor/ /r/n //擷取目前的工作目錄 PWD temp/ /r/n //改變目前的工作目錄 RETR /r/n //開始傳送檔案 PASV /r/n //進入被動模式 PORT h1,h2,h3,h4,p1,p2 /r/n //進入主動模式,h1,h2,h3,h4為ip位址的4個部分。p1,p2是16進制的端口号

  下面介紹一下各個函數的使用順序和一些應注意的地方:

  使用這些指令的前提條件是用戶端和伺服器端建立了連接配接。比如ftp伺服器位址:192.168.1.81 ,端口:21。那麼利用Winsock的API函數建立socket連接配接,然後使用USER,PASS登陸FTP伺服器.需要下載下傳檔案,要確定檔案必須在目前工作目錄下,可以使用指令CWD和PWD。檢視和更改目前的工作目錄。使用SIZE指令擷取檔案的大小。我們想要多線程下載下傳那麼就要求伺服器支援該功能。一般我們都會在開頭先使用REST指令判斷該ftp站點是否支援多線程下載下傳。PORT和PASV兩個指令是用來建立資料連接配接的。他們的主要差別是:PORT需要你指定一個ip位址和端口與伺服器建立連接配接。PASV指令伺服器會傳回h1,h2,h3,h4,p1,p2樣式 的資料供用戶端連接配接。等資料連接配接建立後,就可以了使用REST,RETR進行多線程和斷點續傳檔案下載下傳了。

  上面講解了一點ftp下載下傳的基本知識,下面主要介紹的是斷點續傳的檔案儲存技巧。

  若要講斷點續傳的檔案儲存方式至少可以說出10種,但是各種方法都有利有弊,下面主要介紹一種我在工作中常常使用的一種檔案儲存方式:比如要下載下傳一個364544位元組的檔案,檔案名為:namelock.avi。因為要斷點續傳,是以 在下載下傳的過程中必須得儲存檔案的大小,已經下載下傳的檔案的大小和各個線程的任務。

  有兩種方法:

  一、可以産生兩個檔案:内容檔案和配置檔案。

  二、隻需一個檔案:把配置檔案的資料加載到内容檔案的末尾。

  這兩個都不失為好方法。我使用的是前一種,因為我水準有限,(對于臨界資源的通路總是不能做到互坼,老出問題。)。這裡 的字尾名希望大家要把它放在心上,字尾名是個象征性的東西。就拿我們公司來說,擁有自己的MPEG編碼、解碼技術,比如原來5m的一首mp3歌曲,通過編碼可以 轉換成500K左右的.fun檔案(funinhand的前三個字)。再利用我們自己的解碼播放器邊下載下傳邊解碼邊播放, 音質和mp3不相上下。真正實作了手機上的流媒體技術。受到國内外高科技大公司的信賴。(不好意思,這裡有點像做廣告了。)講這些的另外一個企圖是這樣的:

  内容檔案所使用的字尾名是我女朋友的英文名(namelock)的前三個字母.nam 。配置檔案使用的是我自己的英文名(sandy)的前三個字母.san 。是以說寫程式也可以很浪漫,因為這,女朋友又給了我的月生活零用錢增加了幾元,哈哈(大家也可以效仿)。言歸正傳,這兩個檔案嚴格意義上來講是臨時檔案,當檔案下載下傳完畢的時候,namelock.avi.nam内容檔案應該改名為:namelock.avi。namelock.avi.san配置檔案也應該及時的删除。

FTP多線程下載下傳技術部分:前面我介紹了檔案的儲存技巧,主要也是為了多線程服務。現在有個namelock.avi檔案需要下載下傳。檔案的大小為:364544位元組。要用8個下載下傳線程。 第一步:将namelock.avi檔案分成8個子子產品。這裡要注意的地方是我所說的分成8個字子產品,并不是把檔案的内容分别存放到8個不同的緩沖區裡。而是生成8個不同的檔案偏移量。很多時候程式員為了偷懶往往容易一次性講檔案讀入記憶體,這樣帶來的後果是不堪設想的。一個比較理想的方法是這樣的。

bool DealFile(string fileName) //随便寫個函數說明 { FILE *file; DWORD fileSize ,pos; int readLen ; //MAX_BUFFER_LEN 在頭檔案裡定義,這裡能夠保證資料不丢失,也不至于記憶體逸出 char *buffer = new char[MAX_BUFFER_LEN]; file = fopen(fileName.c_str(),"r+b");   if(file == NULL) return false; fseek(file,0,2); fileSize = ftell(file); //取得檔案的大小 fseek(file,0,0); do{ readLen = fread(buffer,sizeof(char),MAX_BUFFER_LEN,file); if(readLen > 0) { pos += readLen; //對讀取的檔案做處理 } }while(pos < fileSize); //循環讀取檔案  delete[] buffer; fclose(file); //釋放資源 return true; }

  8個線程下載下傳檔案時,都要對内容檔案和配置檔案進行讀寫。這樣如果沒有處理好,很有可能會造成通路檔案失敗,我定義了一個全局變量FileLocked,如果FileLocked=true說明檔案正在被某個線程通路。是以使用Sleep(10)睡眠等待。當某個線程進入讀寫檔案時必須設定FileLocked = true;通路檔案完畢必須将FileLocked = false;這樣就能很好的控制各個線程對檔案的通路了。(對臨界資源的通路有API提供了很多很好的解決方法,請查閱)。

  8個下載下傳線程同時下載下傳檔案時,完成部分下載下傳是随機的。那麼怎麼樣把随機的檔案資料按照偏移量正确的寫入檔案呢?我是這樣實作的,當要下載下傳檔案namelock.avi時,首先查找檔案namelock.avi.san配置檔案是否存在。如果存在,說明上次已經下載下傳過部分該檔案,就可以斷點續傳了。如果沒有找到該檔案,那麼生成和該檔案的大小一樣大的檔案,檔案裡所有的資料都為0,(可以使用函數memset(buffer,10000,''0''))和一個配置檔案。然後利用fseek函數将資料正确的覆寫原先的0;接下來要介紹一寫配置檔案的格式了。

  很簡單,配置檔案的内容主要包括:檔案在本地儲存的絕對路徑、檔案的大小、線程的個數、已經下載下傳的檔案大小,各個線程的任務(在原始檔案起始位置和結束位置,中間使用''-''分開);如:

D:/mm/namelock.avi //檔案儲存在這裡 364544 //檔案大小 5 //有5個線程在下載下傳 0 //已經下載下傳了0位元組 0-72908 //線程1的下載下傳任務 72908-145816 //線程2的下載下傳任務 145816-218724 //線程3的下載下傳任務 218724-291632 //線程4的下載下傳任務 291632-364544 //線程5的下載下傳任務

  以上是開始下載下傳時的各個線程的任務配置設定。

D:/mm/namelock.avi 364544 5 113868 72908-72908 113868-145816 145816-218724 218724-291632 291632-364544

  以上是某一時刻各個線程的任務配置設定情況。

  各個線程任務配置設定是這樣實作的。在開始下載下傳時,檔案平均分成若幹塊進行下載下傳。如第一個線程一開始的任務是從檔案的0位置開始下載下傳一直到72908位置處。線程1每次下載下傳一塊資料後就要調整任務,如第一次下載下傳了20800位元組的資料,那麼線程1的任務将改為:20800-72908。如此下去,直到任務為72908-72908時表示線程1完成了目前的下載下傳任務。此時,線程1就分析各個線程的任務,找出任務最為繁忙的一個線程:如線程3:14816-218724。那麼線程1就自動去調整任務,拿50%的任務來再次下載下傳。周而複始直到各個線程都完成任務。不過這裡有一點需要注意:為了避免重複下載下傳部分資料,在調整任務的時候,起始的檔案便移量必須加上接受緩沖器的位元組數,因為如前面所舉的列子來看。線程1和線程3在平衡負載的時候,線程正在下載下傳資料,如果所剩的資料比接受緩沖器的大小還小,線程1和線程3的部分下載下傳資料将會重複。

  在調整任務和分析任務的時候,會發現一個問題。就是讀取檔案資料太過頻繁。于是我用了一個資料結構。在下載下傳檔案的過程中始終打開配置檔案,這樣速度提高了很多。在檔案下載下傳完畢後關閉檔案。資料結構如下:

typedef struct FromToImpl{ DWORD from; //任務起始位置 DWORD to; //任務結束位置 }m_fromTo; typedef struct InfroImpl{ String fileLoad; //檔案儲存位置 DWORD fileSize; //檔案大小 int threadCnt; //下載下傳線程數 DWORD alreadyDownloadCnt; //已經下載下傳的檔案大小 FromToImpl *fromToImpl; //各個線程的任務描述 }m_inforImpl;

  具體實作的細節,請檢視 源程式 。