实现FTP多线程下载 (vc)

  现在有不少软件可实现多线程下载.NetAnts,JetCar,其实多线程下载的原理并不复杂,主要的两项技术就是多线程和断点下载。程序中开启多个线程,每个线程利用断点下载,分别下载文件的不同部分,下载完后合并,就可以了。多线程编程很多书已有介绍,这里不再重复,关于断点下载,MFC中提供的CInternetFile类可实现HTTP的断点下载,但无法实现FTP的断点下载。因此,我们只好从FTP协议中的命令入手,自己编写个FTP类,来实现多线程下载。本人编写的CMultiFTP类(在WIN2000+IIS50下测试成功)已在CSDN发表。

FTP指令的详细信息,大家可从http://info.internet.isi.edu/in-notes/rfc/处获得,这里给大家介绍下与多线程下载有关的几个指令极其格式:

USERUSERNAME〉:登陆FTP的用户名,执行成功返回220

PASSPASSWORD〉:密码,执行成功返回230

RESTPOS〉:指定文件下载的开始位置,执行成功返回350

SIZEFILENAME〉:文件大小,执行成功返回213

PASV:建立数据连接,同时取得FTP服务器下载文件时用的端口号,执行成功返回227

TYPE:指定下载文件的类型,参数为I是二进制文件,为A是字符文件,执行成功返回200

RETRFILENAME〉:下载文件,执行成功返回125

这些命令中,RESTRETRSIZE三个命令最关键,在后面会给大家更详细的说明,另外执行FTP命令,FTP服务器会向客户端返回一代码,命令执行成功的代码上面已给出。向服务器发送命令,可把命令当作字符串向服务器发送,如:send(socket,”rest 100/r/n”,…)(注意:要在命令后加/r/n)

在介绍多线程下载前,先给大家介绍下连接FTP服务器和从FTP服务器下载文件的过程。连接FTP SERVER很简单,创建一套接字,指定服务器的地址和端口号,连接到服务器,再向它发送USERPASS命令,服务器返回230,就代表登陆成功,并且服务器和客户建立了一控制连接。

FTP服务器下载文件的过程比较复杂。首先,客户端要和服务器建立一数据连接,可用PORTPASV命令建立数据连接,PORT命令要自己指定一端口号用于下载,PASV命令则由服务器分配一端口号,客户端可从服务器的返回信息提取端口号,返回信息的格式为:

(服务器IP,端口号),本人的程序将使用PASV命令。然后向服务器发送RETR命令下载文件,或先发送一REST命令指明从哪下载文件。之后,要建立一新的套接字,连接到数据连接指定的端口,文件数据就从这个套接字下载。下载完毕后,关闭套接字。

现在进入本篇的精华,实现多线程下载。执行完登陆操作后,先发送“REST 100”命令,测试下服务器是否支持断点下载,如返回成功代码,就可实现多线程下载;然后发送“SIZE”,取得文件的大小,根据文件大小,将文件分为几部分,记下各部分的偏移地址,并作为参数,交给各线程去下载。在下载线程中,先接受主线程传给他的参数(文件名,偏移地址,保存地址等),再发送“PASV”命令,建立数据连接,并新建一套接字连接到新的端口;然后根据文件类型,二进制文件发送“TYPE  I“命令,文本文件发送”TYPE  A“命令;之后发送“REST 〈文件偏移地址〉”命令,通知服务器改变将要下载的文件的开始地址;最后,执行“RETR 〈文件名〉”命令,下载文件。下载完毕后,编段代码合并文件即可。

在这里有个问题,就是主线程如何得知各下载线程已执行完毕。WINDOWS提供了几种线程互斥技术,如CriticalSection,Mutex等,关于他们的详细信息,大家可参考各种编程书籍,在这里我推荐使用CriticalSection技术。可以在程序中建立一全局计数器,在文件下载前置零,并建立一全局CriticalSection变量。在下载线程中,当文件下载完毕后,先锁定全局CriticalSection变量,之后将计数器加一,再释放全局CriticalSection变量。主线程中,可建立一定时,定期检查计数器的值,或让下载线程在下载完毕后调用主线程的某个函数。这样,主线程就可随时发现文件已下载完毕,可合并文件了。

多线程下载的程序设计就是这样,一点都不难。看来掌握某些计算机技术,特别是网络技术,最好还是从实现原理入手,掌握其最精华的部分,激发自己的灵感,编写出个优秀软件。老停留在使用别人的组件和函数库的基础上,你的水平不会有太大提高。


 

为了让大家更好的理解我发表的<<实现FTP多线程下载>>,我把我自己编写的CMultiFTP类贴上来。这段代码我不是很满意(以前写的,请写的较仓促),
但主要供大家参考,更好得理解多线程下载的实现。


// MultiFTP1.cpp: implementation of the CMultiFTP class.
//
//
/*

#include "stdafx.h"
#include "MultiFTP.h"
#include "MultiFTP1.h"
#include <afxmt.h>

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//
// Construction/Destruction
//
#define BUFFERSIZE 5000
CCriticalSection gcs_multiftp;
struct stThreadParam
{
 CString szLocalname;
 CString szRemotename;
 CString szHost;
 CString szUsername,szPassword;
 int ID;
 int nPort;
 CMultiFTP *pFtp;
 UINT uStart,uLength;
};

UINT DownloadThread(LPVOID pVoid)
{
 stThreadParam *pvar=(stThreadParam *)pVoid;
 int ID=pvar->ID ,nPort=pvar->nPort; 
 CString szLocalname=pvar->szLocalname,szRemotename=pvar->szRemotename;
 CString szHost=pvar->szHost ;
 CString szUsername=pvar->szUsername ,szPassword=pvar->szPassword ;
 CMultiFTP *pFtp=pvar->pFtp;
 UINT uStart=pvar->uStart ,uLength=pvar->uLength; 
 delete pvar;
 file://init over
 
 CString szMsg;
 CFTPGetFile m_getfile(pFtp);
 CFile m_file;
 char *pBuffer;

 if(!m_getfile.Connect(szHost,nPort,szUsername,szPassword))
 {
  szMsg.Format("connect to data port fail/r/nID:%d/r/nError:%s",ID,m_getfile.szMsg);
  AfxMessageBox(szMsg);
  return 0;
 }
 m_file.Open(szLocalname,CFile::modeWrite|CFile::modeCreate);
 pBuffer=(char *)VirtualAlloc(NULL,uLength,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
 ASSERT(pBuffer);
 if(!m_getfile.OpenFile(szRemotename,szLocalname,uStart,uLength))
 {
  m_file.Close();
  DeleteFile(szLocalname);
  VirtualFree(pBuffer,0,MEM_RELEASE);
  szMsg.Format("open file fail/r/nID:%d/r/nError:%s",ID,m_getfile.szMsg);
  AfxMessageBox(szMsg);
  return 0;
 }
 file://get file contents
 for(UINT uRead;uLength;uLength-=uRead)
 {
  uRead=m_getfile.Read(pBuffer,uLength); 
  m_file.Write(pBuffer,uRead);
 }
 m_file.Close();
 VirtualFree(pBuffer,0,MEM_RELEASE);
 
 gcs_multiftp.Lock();
 pFtp->OnThreadOver(ID,0,"ok");
 gcs_multiftp.Unlock();
 
 return 0;
}


CMultiFTP::CMultiFTP()
{
 skClient.Create(); 
 nThreads=1;
 blContinue=FALSE;
}

CMultiFTP::~CMultiFTP()
{
 skClient.ShutDown(2);
 skClient.Close(); 
}

int CMultiFTP::GetThreads()
{
 return nThreads;
}

int CMultiFTP::SetThreads(int n)
{
 if(blContinue)
  nThreads=n;
 return nThreads;
}

BOOL CMultiFTP::Connect(CString szHost, UINT Port, CString szUser, CString szPass)
{
 ASSERT(skClient.m_hSocket);
 szHostname=szHost;
 nPort=Port;
 szUsername=szUser;
 szPassword=szPass;
 
 szMsg=="";
 if(!skClient.Connect(szHostname,nPort))
 {
  return FALSE;
 }
 GetMsg();

 CString szCommand;
 szCommand.Format("user %s/r/n",szUsername);
 DoCommand(szCommand);
 szCommand.Format("pass %s",szPassword);
 if(DoCommand(szCommand)!=FTP_LOGOK)
  return FALSE;
 if(szMsg[3]=='-')
  GetMsg();
 if(DoCommand("rest 100")==FTP_RESTOK)
 {
  blContinue=TRUE;
 }
 else
 {
  nThreads=1;
  blContinue=FALSE;
 }
 DoCommand("type a");

 return TRUE;
}

void CMultiFTP::GetMsg()
{
 szMsg="";
 char chMsg[256];
 int nRecv;

    nRecv=skClient.Receive(chMsg,255); 
 if(nRecv==SOCKET_ERROR || nRecv==0) return;
 chMsg[nRecv]='/0';
 szMsg=chMsg;
}

int CMultiFTP::GetReturnCode()
{
 CString szTemp=szMsg;
 szTemp.TrimLeft();
 return atoi(szTemp.Left(3)); 
}

 

int CMultiFTP::DoCommand(CString szCommand)
{
 szCommand+="/r/n";
 if(skClient.Send((LPCTSTR)szCommand,szCommand.GetLength())==SOCKET_ERROR)
  return SOCKET_ERROR;
 GetMsg();
 return GetReturnCode();
}

void CMultiFTP::Close()
{
 skClient.ShutDown(2);
 skClient.Close(); 
}

BOOL CMultiFTP::IsContinue()
{
 return blContinue;
}

void CMultiFTP::OnThreadOver(int ID, UINT uRecv,CString szMsg)
{
 nThreadOvered++;
 if(nThreadOvered==nThreads)
 {
  if(Merge())
   AfxMessageBox("download over");
  else
   AfxMessageBox("download fail");
 }
}

 

BOOL CMultiFTP::Get(CString szRemotepath, CString szLocalpath)
{
 stThreadParam *pvar;
 
 CString szCommand;
 szCommand.Format("size %s",szRemotepath);
 if(DoCommand(szCommand)!=FTP_SIZEOK)
  return FALSE;
 szMsg.Delete(0,4);
 UINT uSize=atoi(szMsg),uAvgSize=uSize/nThreads,uStart=0;
 
 nThreadOvered=0;
 for(int i=1;i<=nThreads;i++)
 {
  pvar=new stThreadParam;
  pvar->ID =i;
     pvar->nPort =nPort;
  pvar->pFtp =this;
  pvar->szHost =szHostname;
  pvar->szPassword =szPassword;
  pvar->szUsername =szUsername;
  pvar->szLocalname.Format("%s_%d.dat",szLocalpath,i); 
  pvar->szRemotename =szRemotepath;
  pvar->uStart =uStart;
  pvar->uLength =(i==nThreads)?uSize:uAvgSize;

  uStart+=pvar->uLength ;
  uSize-=uAvgSize;
  AfxBeginThread(DownloadThread,pvar);
 }

 szLocalname=szLocalpath;
 return TRUE;
}


file://CFTPGetFile class
CFTPGetFile::CFTPGetFile(CMultiFTP *pFtp2)
{
 pFtp=pFtp2;
 blFileopened=FALSE;
}

CFTPGetFile::~CFTPGetFile()
{
 Close();
}

BOOL CFTPGetFile::OpenFile(CString szRemotename,CString szLocalname,UINT uStart2,UINT uLength2)
{
 ASSERT(skClient.m_hSocket); 
 if(skData.m_hSocket)
  skData.Close();
 
 uStart=uStart2;
 uLength=uLength2;
 CString szCommand,szHost;
 int iPort;

 file://open remote file
 file://get data transfer port
 if(DoCommand("pasv/r/n")!=FTP_PASVOK)
 {
  szMsg.Format("openfile:pasv command fail,code:%d",GetFtpCode());
  AfxMessageBox(szMsg);
  return FALSE;
 }
 int p1,p2,i1,i2;
 int iStart=szMsg.ReverseFind('(');
 for(int i=iStart,count=0;i<szMsg.GetLength();i++)
 {
  if(szMsg[i]==',')
  {
   count++;
   if(count==4) p1=i+1;
   if(count==5) p2=i-1;
  }
 }
 i1=atoi(szMsg.Mid(p1,p2-p1+1));
 i2=atoi(szMsg.Mid(p2+2));
 iPort=(i1<<8)+i2;
 szHost=szMsg.Mid(iStart+1,p1-iStart-2);
 szHost.Replace(",",".");
 file://set file mode to i
 DoCommand("type i"); 
 file://reset remote file pointer
 szCommand.Format("rest  %d",uStart);
 DoCommand(szCommand);
 ASSERT(GetFtpCode()==FTP_RESTOK);
 file://get file command
 szCommand.Format("retr %s",szRemotename);
 DoCommand(szCommand,FALSE);

 skData.Create();
 if(!skData.Connect(szHost,iPort))
 {
  skData.Close(); 
  szMsg.Format("openfile:Connnect to data port:%d at server:%s fail",iPort,szHost);
  AfxMessageBox(szMsg);
  return FALSE;
 }
 
 return TRUE;
}

 


int CFTPGetFile::DoCommand(CString szCommand,BOOL blGetMsg)
{
 int nRecv;
 szCommand+="/r/n";
 skClient.Send((LPCTSTR)szCommand,szCommand.GetLength());
 if(blGetMsg)
 {
  nRecv=GetMsg();
     if(nRecv==0 || nRecv==SOCKET_ERROR) 
   return nRecv;
     return GetFtpCode();
 }
 return 0;
}

int CFTPGetFile::GetFtpCode()
{
 szMsg.TrimLeft();
 return atoi(szMsg);
}

BOOL CFTPGetFile::Connect(CString szHostname, int iPort, CString szUser, CString szPass)
{
 if(skClient.m_hSocket)
  skClient.Close();
 skClient.Create();
 ASSERT(skClient.m_hSocket); 
 if(!skClient.Connect(szHostname,iPort))
 {
  szMsg.Format("GetFile:connect to server fail:%d",GetLastError()); 
  AfxMessageBox(szMsg);
  Close();
  return FALSE;
 }
 
 CString szCommand;
 szCommand.Format("user %s/r/n",szUser);
 DoCommand(szCommand);
 szCommand.Format("pass %s/r/n",szPass);
 DoCommand(szCommand);
 if(szMsg[0]=='-')
  GetMsg();
 if(GetFtpCode()!=FTP_LOGOK)
 {
  szMsg.Format("GetFile:password is not correct");
  AfxMessageBox(szMsg);
  return FALSE;
 }
 DoCommand("type a/r/n");

 return TRUE;
}

void CFTPGetFile::Close()
{
 if(skClient.m_hSocket)
 {
  skClient.ShutDown(2);
  skClient.Close();
 }
 if(skData.m_hSocket)
 {
  skData.ShutDown(2);
  skData.Close();
 }
}

int CFTPGetFile::GetMsg()
{
 int nRecv;
 szMsg="";
 nRecv=skClient.Receive(chRecv,255);
 if(nRecv==SOCKET_ERROR)
  return nRecv;
 chRecv[nRecv]='/0';
 szMsg=chRecv;

 return nRecv;
}

 

int CFTPGetFile::Read(char *pBuffer, int nBufferSize)
{
 return skData.Receive(pBuffer,nBufferSize); 
}

BOOL CMultiFTP::Merge()
{
 CFileFind m_find;
 CString szFilename;
 DWORD dwSize;
 for(int i=1;i<=nThreads;i++)
 {
  szFilename.Format("%s_%d.dat",szLocalname,i);
  if(!m_find.FindFile(szFilename))
   return FALSE;
  if(i==nThreads)
  {
   m_find.FindNextFile();
   dwSize=m_find.GetLength(); 
  }
  m_find.Close(); 
 }


 CFile m_file,m_file2;
 char *pBuffer;
 pBuffer=(char *)VirtualAlloc(NULL,dwSize,MEM_RESERVE|MEM_COMMIT,PAGE_READWRITE);
 m_file.Open(szLocalname,CFile::modeCreate|CFile::modeWrite);
 for(i=1;i<=nThreads;i++)
 {
  szFilename.Format("%s_%d.dat",szLocalname,i);
  m_file2.Open(szFilename,CFile::modeRead);
  m_file2.Read(pBuffer,m_file2.GetLength());  
  m_file.Write(pBuffer,m_file2.GetLength());  
  m_file2.Close();
 // DeleteFile(szFilename);
 }
 m_file.Close();
 VirtualFree(pBuffer,0,MEM_RELEASE);
 return TRUE;
}


相关文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值