Introduction
This article is a brief description on how to code to download multiple files from an FTP server using FTPWebRequest
. It is assumed that the reader has knowledge about the basic concepts of FTP (File Transfer Protocol) and C++.
Development Platform
- Operating System: Windows Server 2003
- Language: Managed C++ (new syntax)
- Environment: Visual Studio 2005 (.NET 2.0)
Background
.NET 2.0 provides the FtpWebRequest
and FtpWebResponse
classes to facilitate communication with FTP servers. The two classes are similar to HttpWebRequest
and HttpWebResponse
, respectively.
To initiate, first a request to the FTP server will be initiated, requesting the file needed.
FtpWebRequest ^objRequest = (FtpWebRequest^)WebRequest::Create(
"ftp://servername:portnumber/filename");
Then, we have to get the response for this request:
FtpWebResponse ^objResponse =
(FtpWebResponse^) objRequest->GetResponse();
This response is a raw byte stream consisting of the file that was requested from the server. This stream is then read to get the file requested from the server. This concept is explained in detail below.
Until now, what we have learned is that we could only download one file at a time. What if we need to download multiple files or if we need to download the entire directory structure (files and sub-directories etc.) from an FTP server. As of now, there is no such API in .NET that could help us. So, we have to do things the hard way.
The only help .NET provides is that we can fetch the detailed listing of a directory residing on an FTP server. (That listing contains all the files and sub-directories contained in the directory.)
The response is received as a stream of raw bytes, and:
- we have to collect that string,
- and parse it to get the names of all the files and sub-directories,
- and then loop through the list and request all the files one by one.
- And, if the item in list happens to be a sub-directory, then again, fetch the detailed listing for it, and fetch all the files/directories contained there in one by one...
Recursion will definitely help us ;)
Using the code
Let's make things clear by using the code:
Make a new project in Visual Studio 2005, Managed C++. Create a class FTPClient
. The header file (.h) will look like as:
namespace FTPClientInfo
{
public ref struct FileStruct
{
public:
bool IsDirectory; String ^Name; };
public ref class FTPClient
{
private:
String ^pszError;
array<FileStruct^>^ GetListFromStringData(String ^strFileListData);
public:
bool DownloadFile(String ^pszServerIP, int PortNumber,
String ^pszUserName, String ^pszPassword,
String ^pszFileName, String ^pszSourcePath,
String ^pszDestinationPath);
bool DownloadDirectoryContents(String ^pszServerIP, int iPortNumber,
String ^pszUserName, String ^pszPassword,
String ^pszSourcePath,
String ^pszDestinationPath);
String^ GetError();
};
}
Now, we provide the body for these function in a CPP file:
bool FTPClient::DownloadDirectoryContents(String ^pszServerIP,
int iPortNumber, String ^pszUserName, String ^pszPassword,
String ^pszSourcePath, String ^pszDestinationPath)
{
try
{
String ^pszServerAddress = String::Concat("ftp://",
pszServerIP, ":", Convert::ToString(iPortNumber));
String ^pszRequestURL =
String::Concat(pszServerAddress, pszSourcePath);
FtpWebRequest ^objRequest =
(FtpWebRequest^)WebRequest::Create(pszRequestURL);
NetworkCredential^ myCred =
gcnew NetworkCredential(pszUserName, pszPassword);
objRequest->Credentials::set(myCred);
objRequest->Method =
WebRequestMethods::Ftp::ListDirectoryDetails;
FtpWebResponse ^objResponse =
(FtpWebResponse^) objRequest->GetResponse();
Stream ^objResponseStream = objResponse->GetResponseStream();
StreamReader ^reader = gcnew StreamReader(objResponseStream);
String ^strFileListData = reader->ReadToEnd();
array<FileStruct^> ^arrFiles =
GetListFromStringData(strFileListData);
for(int i = 0; i < arrFiles->Length; i++)
{
if(arrFiles[i]->IsDirectory == true)
{
String ^pszNewDestinationPath =
Path::Combine(pszDestinationPath, arrFiles[i]->Name);
String ^pszNewSourcePath =
Path::Combine(pszSourcePath, arrFiles[i]->Name);
Directory::CreateDirectory(pszNewDestinationPath);
if(false == DownloadDirectoryContents(pszServerIP, iPortNumber,
pszUserName, pszPassword, pszNewSourcePath,
pszNewDestinationPath))
return false;
}
else
{
if(false == DownloadFile(pszServerIP, iPortNumber,
pszUserName, pszPassword, arrFiles[i]->Name,
pszSourcePath, pszDestinationPath))
return false;
}
}
objResponseStream->Close();
return true;
}
catch(Exception ^ex)
{
pszError = String::Copy(ex->Message);
return false;
}
}
array<FileStruct^>^ FTPClient::GetListFromStringData(String ^strFileListData)
{
try
{
array<Char> ^separator = {'\r','\n'};
array<String^> ^arrList = strFileListData->Split(separator,
StringSplitOptions::RemoveEmptyEntries);
array<FileStruct^> ^arrFiles =
gcnew array<FileStruct^>(arrList->Length);
for(int i = 0; i < arrList->Length; i++)
{
FileStruct ^objFile = gcnew FileStruct();
String ^strRecord = arrList[i]->Trim();
String ^strName = strRecord->Substring(39);
objFile->Name = strName;
if(strRecord->Substring(24, 5) == "<DIR>")
objFile->IsDirectory = true;
else
objFile->IsDirectory = false;
arrFiles[i] = objFile;
}
return arrFiles;
}
catch(Exception ^ex)
{
throw gcnew Exception(ex->Message);
}
}
bool FTPClient::DownloadFile(String ^pszServerIP, int iPortNumber,
String ^pszUserName, String ^pszPassword,
String ^pszFileName, String ^pszSourcePath,
String ^pszDestinationPath)
{
try
{
String ^pszServerAddress = String::Concat("ftp://",
pszServerIP, ":",
Convert::ToString(iPortNumber));
String ^pszFile = Path::Combine(pszSourcePath, pszFileName);
String ^pszRequestURL = String::Concat(pszServerAddress, pszFile);
FtpWebRequest ^objRequest =
(FtpWebRequest^)WebRequest::Create(pszRequestURL);
NetworkCredential^ myCred =
gcnew NetworkCredential(pszUserName, pszPassword);
objRequest->Credentials::set(myCred);
objRequest->Method = WebRequestMethods::Ftp::DownloadFile;
FtpWebResponse ^objResponse =
(FtpWebResponse^) objRequest->GetResponse();
Stream ^objResponseStream =
objResponse->GetResponseStream();
FileStream ^objFileStream =
File::Create(Path::Combine(pszDestinationPath, pszFileName));
array [Byte] ^ buffer = gcnew array[Byte](1024);
int nBytesRead;
while(true)
{
nBytesRead = objResponseStream->Read(buffer, 0, 1024);
if(0 == nBytesRead)
break;
objFileStream->Write(buffer, 0, nBytesRead);
objFileStream->Flush();
}
objResponseStream->Close();
objFileStream->Close();
return true;
}
catch(Exception ^ex)
{
pszError = String::Copy(ex->Message);
return false;
}
}
String^ FTPClient::GetError()
{
return pszError;
}
Now we need to test this FTP client. Test it using the following code:
FTPClient ^oFTPClient = gcnew FTPClient();
String ^pszServerIP = "127.0.0.1";
int iPortNumber = 21;
String ^pszUserName = "Administrator";
String ^pszPassword = "password";
String ^pszSourcePath = "/sample/";
String ^pszDestinationPath = "c:/FTP";
bool bDownload = oFTPClient->DownloadDirectoryContents(pszServerIP,
iPortNumber, pszUserName, pszPassword,
pszSourcePath, pszDestinationPath);
if(bDownload)
{
Console::WriteLine("File Download OK");
}
else
Console::WriteLine(String::Concat("Error Occured :: ",
oFTPClient->GetError()));
Conclusion
This explains how to download multiple files using FTPWebRequest
.
The FtpWebRequest
and FtpWebResponse
classes are not available in .NET 1.0 and 1.1. This code therefore will not work on those frameworks.