源程序:(附件)
ftp下載的好處我在這裡就不多說了,許多工程會把ftp下載作為一個重要的功能來實現。微軟提供的WinInet類可以利用下面這些函數:
InternetOpen;
InternetConnect;
GetCurrentDirectory;
SetCurrentDirectory;
FtpGetFile;
很容易實現ftp的下載,網上關於這方面的文章也很多。但是要實現ftp的多線程下載,利用這些函數就顯得有些牽強了。用socket根據ftp協議來開發將會變的十分靈活。下面我就逐步的講解整個開發的過程:開發環境
BCB(組件模式),VC 環境下請自行稍作改動。看了這篇文章後對於BCB開發人員來說,不僅可以對 FlashGet 等軟件的開發原理有一定的了解,特別是在開發組件方面也有很大的指導作用,請耐心的將它看完。很簡單!!
首先介紹一下部分ftp協議:
圖一 FTP服務示意圖
用戶FTP和服務器FTP之間要傳送文件,需要有兩個連接:命令通道和數據連接,從名字上就可以看出命令通道是傳送命令的,數據通道是用於傳送文件。服務器與服務器之間的數據傳送在此就不多作解釋。
主要用到的命令為:USER,PASS,TYPE,SIZE,REST,CWD,PWD,RETR,PASV,PORT,QUIT;
USER:參數是標記用戶的Telnet串。用戶標記是訪問服務器必須的,此命令通常是控制連接後第一個發出的命令,有些主機還會要求口令和帳戶。服務器可以在任何時間接收新的USER命令以改變訪問控制和(或)帳戶信息。這可以重新開始登錄過程,所以傳輸參數不變,在進行中的文件傳輸在過去的訪問控制參數下完成。
PASS:參數是標記用戶口令的Telnet串。此命令緊跟USER命令,在某些站點它是完成訪問控制不可缺少的一步。因此口令是個重要的東西,因此不能顯示出來,服務器方沒有辦法隱藏口令,所以這一任務得由用戶FTP進程完成。
TYPE:參數指定表示類型。有些類型需要第二個參數,第一個參數由單個Telnet字符定義,第二個參數是十進制整數指定字節大小,參數間以<SP>分隔。下面是格式:
圖二 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多線程下載技術部分:前面我介紹了文件的保存技巧,主要也是為了多線程服務。現在有個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
typedef struct FromToImpl{
DWORD from; //任務起始位置
DWORD to; //任務結束位置
}m_fromTo;
typedef struct InfroImpl{
String fileLoad; //文件保存位置
DWORD fileSize; //文件大小
int threadCnt; //下載線程數
DWORD alreadyDownloadCnt; //已經下載的文件大小
FromToImpl *fromToImpl; //各個線程的任務描述
}m_inforImpl;