boost::asio 实现的FTP客户端

时间:2022-09-08 22:38:54

别人的代码,稍作了修改。boost1.50, VS2008可编译成功。

-----------test_ftp.cpp---------------

// ftp_asio.cpp : 定义控制台应用程序的入口点。
//

//#include "stdafx.h"

#pragma warning(disable:4996)

#include <iostream>
#include <istream>
#include <ostream>
#include <fstream>
#include <string>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>

using boost::asio::ip::tcp;
using namespace std;

string strHost, strPort;

//得到文件长度
long GetFileLength(const string& stringLine)
{
long ldFileLength = 0 ;
size_t nBeginPos = stringLine.find_first_of(" ");
size_t nEndPos = stringLine.find_first_of("\r\n");
std::stringstream stream;
string strLength = stringLine.substr(nBeginPos,nEndPos-nBeginPos+1) ;
stream << strLength;
stream >> ldFileLength;
return ldFileLength;
}

//得到ftp返回命令的数值
int GetRetCode(const string& stringLine)
{
if (stringLine.size() < 3)
{
return -1;
}
size_t nPos = stringLine.find_first_of(" ");
std::stringstream stream;
string strCode = stringLine.substr(0,nPos) ;
int nRetCode = 0;
stream << strCode;
stream >> nRetCode;
return nRetCode;
}
//
bool SendDataCommand(tcp::socket& socket, string strCommand, string& strResult)
{
boost::asio::streambuf request;
boost::asio::streambuf response;
std::string strLine ;
std::ostream request_stream(&request);
std::istream response_stream(&response);

strCommand += ("\r\n");
cout<<endl<<"command is :"<<strCommand<<endl;
request_stream <<strCommand;
boost::asio::write(socket, request);
return true;
}
// 发送命令到FTP服务器

bool SendCommand(tcp::socket& socket, string strCommand, string& strResult)
{
boost::asio::streambuf request;
boost::asio::streambuf response;
std::string strLine ;
std::ostream request_stream(&request);
std::istream response_stream(&response);

strCommand += ("\r\n");
cout<<endl<<"command is :"<<strCommand<<endl;
request_stream <<strCommand;
boost::asio::write(socket, request);
boost::asio::read_until(socket, response, "\r\n");
std::getline(response_stream, strResult);
return true;
}

bool GetHostAndPort(string& strResult)
{
size_t nBegin = strResult.find("(");
size_t nEnd = strResult.find(")");

if (nBegin==-1 || nEnd==-1)
return false ;

strHost = strResult.substr(nBegin+1, nEnd-nBegin-1);
nBegin = strHost.find_last_of(",");
strPort = strHost.substr(nBegin+1, nEnd-nBegin+1);
int nPort = 0, tmp=0;

tmp = boost::lexical_cast<int>(strPort);
strHost = strHost.substr(0,nBegin);

nBegin = strHost.find_last_of(",");
strPort = strHost.substr(nBegin+1, nEnd-nBegin+1);
nPort = boost::lexical_cast<int>(strPort);
nPort = 256*nPort + tmp;
strPort = boost::lexical_cast<std::string>(nPort);

return true;
}


bool OpenControl(tcp::socket& socket)
{
bool bRet = false ;
string strResult;

SendCommand(socket,"", strResult);
std::cout<<strResult<<endl;
cout<<"Ret Code is:"<<GetRetCode(strResult)<<endl;

/// send USER
//SendCommand(socket,"USER anonymous", strResult);
SendCommand(socket,"USER 5xue", strResult);
std::cout<<strResult<<endl;
cout<<"User Ret Code is:"<<GetRetCode(strResult)<<endl;

/// send PASSWORD
//SendCommand(socket,"PASS anonymous", strResult);
SendCommand(socket,"PASS 5xue.com", strResult);
std::cout<<strResult<<endl;
cout<<"User Ret Code is:"<<GetRetCode(strResult)<<endl;

//send TYPE 1
SendCommand(socket,"TYPE I", strResult);
std::cout<<strResult<<endl;
cout<<"User Ret Code is:"<<GetRetCode(strResult)<<endl;

//send SIZE
//string strObject = "incoming/ProcessExplorer/procexp.exe";
string strObject = "upload/ReadMe.txt";
//string strObject = "upload/计算机程序设计艺术第三版第一卷:基本算法.pdf";
string strCommand("SIZE ");
strCommand += strObject;

SendCommand(socket, strCommand, strResult);
std::cout<<strResult<<endl;
cout<<"User Ret Code is:"<<GetRetCode(strResult)<<endl;

long ldFileSize = GetFileLength(strResult);
cout<<"FileSize is:"<<ldFileSize<<endl;
if (ldFileSize>0)
{
bRet = true;
}

////send TYPE 1
//SendCommand(socket,"TYPE I", strResult);
//std::cout<<strResult<<endl;
//cout<<"TYPE 1 Ret Code is:"<<GetRetCode(strResult)<<endl;

//send PASV
SendCommand(socket,"PASV", strResult);
std::cout<<strResult<<endl;
cout<<"PASV Ret Code is:"<<GetRetCode(strResult)<<endl;

bRet = GetHostAndPort(strResult);


strCommand="RETR " ;
strCommand += strObject;
SendDataCommand(socket,strCommand, strResult);

return bRet;
}

bool OpenDataChannel(tcp::socket& data_socket)
{
bool bRet = false;


boost::asio::streambuf response;
//asio::error_code error;
boost::system::error_code error;

if (response.size() > 0)
std::cout << &response;

ofstream file;
file.open("file.txt",ios_base::binary);

// Read until EOF, writing data to output as we go.
while (boost::asio::read(data_socket, response,
boost::asio::transfer_at_least(1), error))
{
std::cout << &response;
//file << &response;
}
file.close();
if (error != boost::asio::error::eof)
//throw boost::asio::system_error(error);
throw boost::system::error_code(error);

return bRet;
}


int main(int argc, char* argv[])
{

////用asio进行网络连接至少需要一个boost::asio::io_service对象
boost::asio::io_service io_service;

// 我们需要把在命令行参数中指定的服务器转换为TCP上的节点.
// 完成这项工作需要boost::asio::ip::tcp::resolver对象
tcp::resolver time_server_resolver(io_service);

//一个resolver对象查询一个参数,并将其转换为TCP上节点的列表.
//tcp::resolver::query query("192.168.1.111","ftp");
tcp::resolver::query query("ftp.5xue.com","ftp");

//节点列表可以用 boost::asio::ip::tcp::resolver::iterator 来进行迭代.
// iterator默认的构造函数生成一个end iterator

tcp::resolver::iterator endpoint_iterator = time_server_resolver.resolve(query);
tcp::resolver::iterator end;

//现在我们建立一个连接的sockert,由于获得节点既有IPv4也有IPv6的.
// 所以,我们需要依次尝试他们直到找到一个可以正常工作的.
// 这步使得我们的程序独立于IP版本

tcp::socket socket(io_service);
//boost::asio::error_code error = boost::asio::error::host_not_found;
boost::system::error_code error = boost::asio::error::host_not_found;
while (error && endpoint_iterator != end)
{
socket.close();
socket.connect(*endpoint_iterator++, error);;
}

if (error)
{
//throw boost::asio::system_error(error);
throw boost::system::system_error(error);
}

//commu with ftp server
bool bState = OpenControl(socket);

if (!bState)
return -1;

tcp::socket data_socket(io_service);
//tcp::resolver::query data_query("192.168.1.111",strPort);
tcp::resolver::query data_query("ftp.5xue.com",strPort);

tcp::resolver::iterator data_endpoint_iterator = time_server_resolver.resolve(data_query);
error = boost::asio::error::host_not_found;

if(error && data_endpoint_iterator != end)
{
data_socket.close();
data_socket.connect(*data_endpoint_iterator++, error);
cout << "data_socket.close(): "<< error.value() << " \t" << error.message() << endl;
}

if (error)
{
//throw boost::asio::system_error(error);
throw boost::system::system_error(error);
}
OpenDataChannel(data_socket);

return 0;
}


 

-----------------------------------------

FTP命令

ftp help

214-The following commands are recognized (* =>'s unimplemented):

 CWD     XCWD    CDUP    XCUP    SMNT*   QUIT    PORT    PASV   

 EPRT    EPSV    ALLO*   RNFR    RNTO    DELE    MDTM    RMD    

 XRMD    MKD     XMKD    PWD     XPWD    SIZE    SYST    HELP   

 NOOP    FEAT    OPTS    AUTH*   CCC*    CONF*   ENC*    MIC*   

 PBSZ*   PROT*   TYPE    STRU    MODE    RETR    STOR    STOU   

 APPE    REST    ABOR    USER    PASS    ACCT*   REIN*   LIST   

 NLST    STAT    SITE   

FTP协议中命令

2011-04-23 17:00:25| 分类: FTP | 标签:ftp命令 cwd pwd list port |字号 订阅

4.1. FTP命令(这里指的不是命令行下的FTP命令,而是协议中用来服务器和客户端交流的)
4.1.1. 访问控制命令
下列命令指定访问控制标记(命令码在括号内):
用户名(USER)
参 数是标记用户的Telnet串。用户标记是访问服务器必须的,此命令通常是控制连接后第一个发出的命令,有些主机还会要求口令和帐户。服务器可以在任何时 间接收新的USER命令以改变访问控制和(或)帐户信息。这可以重新开始登录过程,所以传输参数不变,在进行中的文件传输在过去的访问控制参数下完成。
口令(PASS)
参数是标记用户口令的Telnet串。此命令紧跟USER命令,在某些站点它是完成访问控制不可缺少的一步。因此口令是个重要的东西,因此不能显示出来,服务器方没有办法隐藏口令,所以这一任务得由用户FTP进程完成。
ACCOUNT (ACCT)
 
参 数是标记用户帐户的Telnet串。此命令不需要与USER相关,一些站点可能需要帐户用于登录,另一些可以限制帐户的权限,在后一种情况下,此命令可在 任何时候发送。应答的不同可以区别不同的情况:当登录需要帐户信息时,对PASS命令的响应是332。另外,如果不需要帐户信息,对PASS的响应是 230,如果需要帐户信息在以后需要,服务器会返回332或532,这要看它是保存此命令还是拒绝此命令了。
改变工作目录(CWD)
此命令使用户可以在不同的目录或数据集下工作而不用改变它的登录或帐户信息。传输参数也不变。参数一般是目录名或与系统相关的文件集合。
回到上一层目录(CDUP)
此命令要求系统实现目录树结构,它的响应和CWD的相同。
结构加载(SMNT)
此命令使用户在不改变登录或帐户信息的情况下加载另一个文件系统数据结构。传输参数也不变。参数是文件目录或与系统相关的文件集合。
重新初始化(REIN)
此命令终止USER,将所有I/O和帐户信息写入,但不许进行中的数据传输完成。重置所有参数,控制连接打开,可以再次开始USER命令。
退出登录(QUIT)
此 命令终止USER,如果没有数据传输,服务器关闭控制连接;如果有数据传输,在得到传输响应后服务器关闭控制连接。如果用户进程正在向不同的USER传输 数据,不希望对每个USER关闭然后再打开,可以使用REIN。对控制连接的意外关闭,可以导致服务器运行中止(ABOR)和退出登录(QUIT)。
4.1.2. 传输参数命令
所有数据传输参数有默认值。服务器必须记录下默认值,在FTP服务请求后,可以以任何顺序发送。下面命令传送参数:
数据端口(PORT)
 
参数是要使用的数据连接端口,通常情况下对此不需要命令响应。如果使用此命令时,要发送32位的IP地址和16位的TCP端口号。上面的信息以8位为一组,逗号间隔十进制传输,如下例:
PORT h1,h2,h3,h4,p1,p2
其中h1是IP地址的最高8位。
被动(PASV)
此命令要求服务器DTP在指定的数据端口侦听,进入被动接收请求的状态,参数是主机和端口地址。
表示类型(TYPE)
参数指定表示类型。有些类型需要第二个参数,第一个参数由单个Telnet字符定义,第二个参数是十进制整数指定字节大小,参数间以<SP>分隔。下面是格式:
默认表示类型是ASCII非打印字符,如果参数未改变,以后只改变了第一个参数,则使用默认值。
文件结构(STRU)
参数是一个Telnet字符代码指定文件结构。下面是代码及其意义:
F - 文件(非记录结构),它是默认值
R - 记录结构
P - 页结构
传输模式(MODE)
参数是一个Telnet字符代码指定传输模式。下面是代码及其意义:
S - 流(默认值)
B - 块
C - 压缩
4.1.3. FTP服务命令
FTP 服务命令定义用户请求的文件传输或文件系统功能。此命令的参数通常是路径名,其语法要和服务器的规范一致。推荐的默认值是最近指定的设备目录或目录。命令 顺序通常没有限制,只有"rename from"命令后面必须是"rename to",重新启动命令后面必须是中断服务命令。服务命令的响应通常在数据连接上传输。下面是具体的命令:
获得文件(RETR)
此命令使服务器DTP传送指定路径内的文件复本到服务器或用户DTP。这边服务器上文件的状态和内容不受影响。
保存(STOR)
此命令使服务器DTP接收数据连接上传送过来的数据,并将数据保存在服务器的文件中。如果文件已存在,原文件将被覆盖。如果文件不存在,则新建文件。
唯一保存(STOU)
此命令和STOR差不多,此命令要求在此目录下的文件名是唯一的,对此命令的响应必须包括产生的用户名。
附加(APPE)
它和STOR的功能差不多,但是如果文件在指定路径内已存在,则把数据附加到原文件尾部,如果不存在则新建文件。
分配(ALLO)
此 命令用于在一些主机上为新传送的文件分配足够的存储空间。参数是十进制的逻辑字节数。如果是记录或页结构,页或记录的最大大小也需要,这在第二个参数内以 十进制指定。第二个参数是可选的,如果有它,它和第一个参数以Telnet字符<SP> R  <SP>分隔。此命令在STOR或APPE命令后,对于不需要分配存储空间的机器,它的作用等于NOOP。
重新开始(REST)
参数域代表服务器要重新开始的那一点,此命令并不传送文件,而是略过指定点后的数据,此命令后应该跟其它要求文件传输的FTP命令。
重命名(RNFR)
这个命令和我们在其它操作系统中使用的一样,只不过后面要跟"rename to"指定新的文件名。
重命名为(RNTO)
此命令和上面的命令共同完成对文件的重命名。
放弃(ABOR)
此命令通知服务中止以前的FTP命令和与之相关的数据传送。如果先前的操作已经完成,则没有动作,返回226。如果没有完成,返回426,然后再返回226。关闭控制连接,数据连接不关闭。
删除(DELE)
此命令删除指定路径下的文件。用户进程负责对删除的提示。
删除目录(RMD)
此命令删除目录。
创建目录(MKD)
此命令在指定路径下创建新目录。
打印工作目录(PWD)
在响应是返回当前工作目录。
列表(LIST)
服 务器传送列表到被动DTP,如果路径指定一个目录或许多文件,返回指定路径下的文件列表。如果路径名指定一个文件,服务器返回文件的当前信息,参数为空表 示用户当前的工作目录或默认目录。数据传输在ASCII或EBCDIC下进行,用户必须确认这一点。因为文件信息因系统不同而不同,所以不可能被程序自动 利用,但是人类用户却很需要。
名字列表(NLST)
服务器传送目录表名到用户,路径名应指定目录或其它系统指定的文件群描述子;空参数指当前目录。服务器返回文件名数据流,以ASCII或EBCDIC形式传送,并以<CRLF>或<NL>分隔。这里返回的信息有时可以供程序进行进一步处理。
站点参数(SITE)
服务器用来提供服务器系统信息,信息因系统不同而不同,格式在HELP SITE命令应答中给出。
系统(SYST)
用于确定服务器上运行的操作系统。
状态(STAT)
此 命令返回控制连接状态,它可以在文件传送过程中发送,服务器返回操作进行的状态。也可以在文件传送之间发送,这时命令有参数,参数是路径名,此命令的功能 除了数据在控制连接上传送以外和列表命令相似。如果指定部分路径,服务器以文件名或与说明相关的属性返回;如没有参数,服务器返回服务器FTP进程的状态 信息,包括传输参数的当前值和连接状态。
帮助(HELP)
这条命令我们在平常系统中得到的帮助没有什么区别,响应类型是211或214。建议在使用USER命令前使用此命令。
等待(NOOP)
此命令不产生什么实际动作,它仅使服务器返回OK。
FTP 在控制连接上使用Telnet通信,因此有机会大家可以看看相关的协议说明。对下文的理解会很有好处。下面内容将对命令的应答和关于命令的详细信息作以说 明。FTP命令可分为访问控制标记,数据传输参数或FTP服务请求,特定的命令(如ABOR,STAT)可以在数据传输过程中在控制连接上传输。有些服务 器不能同时监视数据和控制链路,那就要另外采取措施了。请注意下面的几点建议:
1. 用户系统将Telnet的"Interrupt Process"(IP)信息插入Telnet流;
2. 用户系统发送Telnet的"Synch"信号;
3. 用户系统将命令(如ABOR)插入Telnet流;
4. 服务器PI在接收到IP后,在Telnet流中寻找仅有一个的FTP命令。