一、FTP两种工作模式:
主动模式(Active FTP)和被动模式(Passive FTP)
在主动模式下,FTP客户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,然后开放N+1号端口进行
监听,并向服务器发出PORT N+1命令。服务器接收到命令后,会用其本地的FTP数据端口(通常是20)来连接客户端指
定的端口N+1,进行数据传输。
在被动模式下,FTP库户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,同时会开启N+1号端口。然后向服务器发送PASV命令,通 知服务器自己处于被动模式。服务器收到命令后,会开放一个大于1024的端口P进行监听,然后用PORT P命令通知客户端,自己的数据端口是P。客户端收到命令后,会通过N+1号端口连接服务器的端口P,然后在两个端口之间进行数据传输。
被动模式的FTP通常用在处于防火墙之后的FTP客户访问外界FTp服务器的情况,因为在这种情况下,防火墙通常配置为不允许外界访问防火墙之后主机,而 只允许由防火墙之后的主机发起的连接请求通过。因此,在这种情况下不能使用主动模式的FTP传输,而被动模式的FTP可以良好的工作。
总的来说,主动模式的FTP是指服务器主动连接客户端的数据端口,被动模式的FTP是指服务器被动地等待客户端连接自己的数据端口。
二、最近遇到个烦人的事情是移植的ftp客户端,利用脚本调用的,如果调用的频率很高的情况下,会发现CPU老高了,根本达不到实际的应用要求,机器跑起来也很累,干脆就从经典的ftp客户端源码中挖了一部分特别简单的句子出来了。原来是移植的netkit-ftp-0.17.tar.gz整个包,其实busybox里面应该是有的,当时使用的是被动模式,简单的用脚本限定从根目录到二级目录可传:
#!/bin/sh
############################################################
# examples:
# Tow level dir: ./ftpput.sh "192.168.1.20" "21" "test" "test" "/mnt/config/factory.conf" "/mnt/nfs/factory.conf"
# One level dir: ./ftpput.sh "192.168.1.20" "21" "test" "test" "/mnt/config/factory.conf" "/upload/factory.conf"
# Root dir: ./ftpput.sh "192.168.1.20" "21" "test" "test" "/mnt/config/factory.conf" "factory.conf"
# August 10, 2014, 15:33
##########################################################
if [ $# -ne 6 ]
then
echo "Usage $0 <ip> <port> <user> <password> <local_dir/filename> <remote_dir/filename>"
exit 1
fi
LOCALDIR=`dirname $5`
LOCALFILE=`basename $5`
REMOTEDIR=`dirname $6`
REMOTEFILE=`basename $6`
FTP_PATH=/mnt/bin/ftp
#only root dir, set remotedir to NULL
if [ "$REMOTEDIR" = "." ]; then
REMOTEDIR=""
fi
TOP_REMOTE_DIR=`echo $REMOTEDIR | cut -d "/" -f 2`
SECOND_REMOTE_DIR=`echo $REMOTEDIR | cut -d "/" -f 3`
THIRD_REMOTE_DIR=`echo $REMOTEDIR | cut -d "/" -f 4`
#show info
#echo "LOCALDIR=$LOCALDIR"
#echo "LOCALFILE=$LOCALFILE"
#echo "REMOTEDIR=$REMOTEDIR"
#echo ""
#echo "TOP_REMOTE_DIR=$TOP_REMOTE_DIR"
#echo "SECOND_REMOTE_DIR=$SECOND_REMOTE_DIR"
#echo "THIRD_REMOTE_DIR=$THIRD_REMOTE_DIR"
#echo "REMOTEFILE=$REMOTEFILE"
if [ "$THIRD_REMOTE_DIR" != "" ]; then
echo "third remote dir is not null, exit !"
exit 1
fi
if [ "$SECOND_REMOTE_DIR" != "" ]; then
echo "Two level dir ftp upload, local file=$LOCALDIR/$LOCALFILE, remote file=/$TOP_REMOTE_DIR/$SECOND_REMOTE_DIR/$REMOTEFILE"
$FTP_PATH -i -n $1 $2 <<END_SCRIPT
user $3 $4
bin
passive
mkdir $TOP_REMOTE_DIR
cd $TOP_REMOTE_DIR
mkdir $SECOND_REMOTE_DIR
cd $SECOND_REMOTE_DIR
lcd $LOCALDIR
put $LOCALFILE
quit
END_SCRIPT
else
if [ "$TOP_REMOTE_DIR" != "" ]; then
echo "One level dir ftp upload, local file=$LOCALDIR/$LOCALFILE, remote file=/$TOP_REMOTE_DIR/$REMOTEFILE"
$FTP_PATH -i -n $1 $2 <<END_SCRIPT
user $3 $4
bin
passive
mkdir $TOP_REMOTE_DIR
cd $TOP_REMOTE_DIR
lcd $LOCALDIR
put $LOCALFILE
quit
END_SCRIPT
else
echo "Root dir ftp upload, local file=$LOCALDIR/$LOCALFILE, remote file=$REMOTEFILE"
$FTP_PATH -i -n $1 $2 <<END_SCRIPT
user $3 $4
bin
passive
lcd $LOCALDIR
put $LOCALFILE
quit
END_SCRIPT
fi
fi
exit 0
<span style="font-size:18px;">从源码中挖出一部分出来后,测试的结果还是比较理想的,移植的超级简单,这里做个记录:</span>
<span style="font-size:18px;">1、FtpClient.h</span>
<span style="font-size:18px;"></span><pre class="cpp" name="code">/*
* FtpClient.h
*
* Created on: Aug 23, 2014
* Author: xtank.nie@gmail.com
*/
#ifndef FTP_SOCKET_H_
#define FTP_SOCKET_H_
#include <iostream>
#include <string>
using std::string;
class CFtpClient
{
public:
CFtpClient(void);
~CFtpClient(void);
public:
// Init("192.168.1.80", 21, "test", "test");
int Init(const string &ftpServer, unsigned short port,
const string &user, const string &password);
// 根目录: UploadFile "/tmp/sd0/test.jpg" "/test.jpg"
//一级目录: UploadFile "/tmp/sd0/test.jpg" "/pic/test.jpg"
int UploadFile(const string &localPath, const string &remotePath);
int Deinit(void);
private:
int HookupServer(const string &ftpServer, unsigned short port = 21);
int VerifyFtpUserPassword(const string &user, const string &password);
int PassiveModeFileTransferConnect(void);
int InteractiveMode(void);
int GetFtpServerReply(int expecteof);
int CommonCommand(const char *fmt, ...);
int InitFileTransferConnect(void);
void lcd(const char *dir);
int FtpPutFile(const char *localFileName, const char *remoteFileName);
FILE *DataConn(const char *lmode);
bool HookupOK(void);
int Makedir(const char *dir);
int GotoDir(const char *dir);
private:
FILE *m_commandRead, *m_commandWrite;
int m_connected;
string m_recvContent;
int m_fileTransferSocket;
int m_commandSocket;
string m_localDir, m_remoteDir;
bool m_init;
};
#endif /* FTP_CLIENT_H_ */
//FtpClient.cpp
/*
* FtpClient.cpp
*
* Created on: Aug 23, 2014
* Author: xtank.nie@gmail.com
*/
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/ftp.h>
#include <fcntl.h>
#include "FtpClient.h"
CFtpClient::CFtpClient(void):
m_commandRead(NULL),
m_commandWrite(NULL),
m_connected(0),
m_recvContent(""),
m_fileTransferSocket(-1),
m_commandSocket(-1),
m_init(0)
{
}
CFtpClient::~CFtpClient(void)
{
Deinit();
}
int CFtpClient::Init(const string &ftpServer, unsigned short port,
const string &user, const string &password)
{
if (m_init)
return 0;
int isHookup = -1, checkUserpwd = -1;
if ((isHookup = HookupServer(ftpServer, port)) == 0)
{
checkUserpwd = VerifyFtpUserPassword(user, password);
if (checkUserpwd == 0)
{
if (InteractiveMode() == 0)
{
m_init = true;
fprintf(stderr, "ftp client init ok !\n");
return 0;
}
else
return -1;
}
else
{
fprintf(stderr, "FTP verify username or passwd error !\n");
return -1;
}
}
else
{
fprintf(stderr, "FTP HookupServer error !\n");
return -1;
}
return 0;
}
int CFtpClient::HookupServer(const string &ftpServer, unsigned short port)
{
struct hostent *hp = NULL;
struct sockaddr_in hisctladdr, myctladdr;
socklen_t len;
char hostnamebuf[256] = {0};
memset(&hisctladdr, 0, sizeof(hisctladdr));
if (inet_aton(ftpServer.c_str(), &hisctladdr.sin_addr)) //IP address
{
hisctladdr.sin_family = AF_INET;
strncpy(hostnamebuf, ftpServer.c_str(), sizeof(hostnamebuf));
hostnamebuf[sizeof(hostnamebuf)-1]=0;
fprintf(stderr, "ftp server ipaddr=%s\n", hostnamebuf);
}
else
{
hp = gethostbyname(ftpServer.c_str()); //domain name
if (hp == NULL)
{
fprintf(stderr, "ftp: %s: ", ftpServer.c_str());
herror((char *)NULL);
return -1;
}
hisctladdr.sin_family = hp->h_addrtype;
if (hp->h_length > (int)sizeof(hisctladdr.sin_addr))
hp->h_length = sizeof(hisctladdr.sin_addr);
memcpy(&hisctladdr.sin_addr, hp->h_addr_list[0], hp->h_length);
strncpy(hostnamebuf, hp->h_name, sizeof(hostnamebuf));
hostnamebuf[sizeof(hostnamebuf)-1] = 0;
fprintf(stderr, "ftp server domain=%s(%s)\n", hostnamebuf, inet_ntoa(hisctladdr.sin_addr));
}
if ((m_commandSocket = socket(hisctladdr.sin_family, SOCK_STREAM, 0)) < 0)
{
perror("ftp: socket");
return -1;
}
hisctladdr.sin_port = htons(port);
int i = 0;
while (connect(m_commandSocket, (struct sockaddr *)&hisctladdr, sizeof (hisctladdr)) < 0)
{
++i;
if (hp && hp->h_addr_list[i]) //下一个地址
{
fprintf(stderr, "ftp: connect to address %s: ", inet_ntoa(hisctladdr.sin_addr));
hp->h_addr_list++;
memcpy(&hisctladdr.sin_addr, hp->h_addr_list[0], hp->h_length);
fprintf(stdout, "Trying %s...\n", inet_ntoa(hisctladdr.sin_addr));
close(m_commandSocket);
if ((m_commandSocket = socket(hisctladdr.sin_family, SOCK_STREAM, 0)) < 0)
{
perror("ftp: socket");
return -1;
}
continue;
}
perror("ftp: connect");
goto bad;
}
len = sizeof(myctladdr);
memset(&myctladdr, 0x0, sizeof(struct sockaddr_in));
if (getsockname(m_commandSocket, (struct sockaddr *)&myctladdr, &len) < 0)
{
perror("ftp: getsockname");
goto bad;
}
else
{
printf("client socket info: socket=%d, family=%d, ip=%s, port=%u\n",
m_commandSocket,
myctladdr.sin_family,
inet_ntoa(myctladdr.sin_addr),
ntohs(myctladdr.sin_port));
}
m_commandRead = fdopen(m_commandSocket, "r");
m_commandWrite = fdopen(m_commandSocket, "w");
if (m_commandRead == NULL || m_commandWrite == NULL)
{
fprintf(stderr, "ftp: fdopen failed.\n");
goto bad;
}
if (HookupOK())
{
printf("ftpClient Connected to %s.\n", hostnamebuf);
m_connected = 1;
return 0;
}
bad:
if (m_commandRead)
{
(void) fclose(m_commandRead);
m_commandRead = NULL;
}
if (m_commandWrite)
{
(void) fclose(m_commandWrite);
m_commandWrite = NULL;
}
close(m_commandSocket); m_commandSocket = -1;
m_connected = 0;
m_init = false;
return -1;
}
int CFtpClient::GetFtpServerReply(int expecteof)
{
FRONT:
char c;
m_recvContent = "";
for (;;)
{
while ((c = getc(m_commandRead)) != '\n')
{
if (c == IAC)
{ /* handle telnet commands */
printf("------------IAC");
#if 0
switch (c = getc(m_commandRead))
{
case WILL:
case WONT:
c = getc(m_commandRead);
fprintf(m_commandWrite, "%c%c%c", IAC, DONT, c);
(void) fflush(m_commandWrite);
break;
case DO:
case DONT:
c = getc(m_commandRead);
fprintf(m_commandWrite, "%c%c%c", IAC, WONT, c);
(void) fflush(m_commandWrite);
break;
default:
break;
}
#endif
continue;
}
if (c == EOF)
{
if (expecteof)
{
printf("getc char is EOF.\n");
return (0);
}
printf("421 Service not available, remote server has closed connection\n");
(void) fflush(stdout);
m_recvContent.clear();
return -1;
}
m_recvContent.append(1, c);
}
break;
}
if (m_recvContent[3] != ' ')
{
m_recvContent.clear();
goto FRONT;
}
//printf("m_recvContent: %s\n", m_recvContent.c_str());
return 0;
}
int CFtpClient::VerifyFtpUserPassword(const string &user, const string &password)
{
if (m_connected == 1)
{
CommonCommand("USER %s", user.c_str());
if (m_recvContent.find("331") != m_recvContent.npos)
{
CommonCommand("PASS %s", password.c_str());
if (m_recvContent.find("230") != m_recvContent.npos)
{
fprintf(stderr, "login success !\n");
return 0;
}
}
}
return -1;
}
int CFtpClient::CommonCommand(const char *fmt, ...)
{
va_list ap;
if (m_commandWrite == NULL)
{
perror ("No control connection for command");
return (-1);
}
va_start(ap, fmt);
vfprintf(m_commandWrite, fmt, ap);
va_end(ap);
fprintf(m_commandWrite, "\r\n");
(void) fflush(m_commandWrite);
return GetFtpServerReply(!strcmp(fmt, "QUIT"));
}
int CFtpClient::PassiveModeFileTransferConnect(void)
{
CommonCommand("PASV");
if (m_recvContent.find("227") != m_recvContent.npos) //被动模式
{
InitFileTransferConnect();
return 0;
}
return -1;
}
int CFtpClient::InteractiveMode(void)
{
CommonCommand("TYPE I");
if (m_recvContent.find("200") != m_recvContent.npos)
return 0;
return -1;
}
int CFtpClient::InitFileTransferConnect(void)
{
int on = 1;
u_long a1,a2,a3,a4,p1,p2;
struct sockaddr_in data_addr;
if (m_fileTransferSocket == -1)
{
m_fileTransferSocket = socket(AF_INET, SOCK_STREAM, 0);
if (m_fileTransferSocket < 0)
{
perror("ftp: socket");
return(-1);
}
//printf("m_fileTransferSocket=%d\n", m_fileTransferSocket);
//"227 Entering Passive Mode (192,168,1,219,4,22)"
//被动模式下服务端告诉客户端自己已经开启了那个端口进行监听就绪,客户端可以进行connect
int start = m_recvContent.find('(');
int end = m_recvContent.find(')');
string t_string = "";
t_string.assign(m_recvContent, start+1, end-start-1);
//std::cout << "t_string=" << t_string << std::endl;
if (sscanf(t_string.c_str(),"%ld,%ld,%ld,%ld,%ld,%ld",
&a1,&a2,&a3,&a4,&p1,&p2)
!= 6)
{
printf("Passive mode address scan failure. Shouldn't happen!\n");
return(-1);
}
data_addr.sin_family = AF_INET;
data_addr.sin_addr.s_addr = htonl((a1 << 24) | (a2 << 16) | (a3 << 8) | a4);
data_addr.sin_port = htons((p1 << 8) | p2);
if (connect(m_fileTransferSocket, (struct sockaddr *) &data_addr, sizeof(data_addr))<0)
{
perror("ftp: connect");
close(m_fileTransferSocket);
m_fileTransferSocket = -1;
return(-1);
}
return(0);
}
return 0;
}
int CFtpClient::UploadFile(const string &localPath, const string &remotePath)
{
string local_dir, local_file;
string remote_file, remote_dir;
int local_end_pos = localPath.rfind('/');
int remote_end_pos = remotePath.rfind('/');
if (!m_init)
return -1;
local_dir.assign(localPath, 0, local_end_pos); //
local_file.assign(localPath, local_end_pos+1, localPath.length());
remote_dir.assign(remotePath, 0, remote_end_pos+1);
remote_file.assign(remotePath, remote_end_pos+1, remotePath.length()); //
#if 0
fprintf(stderr, "local dir=%s, local file=%s\n", local_dir.c_str(), local_file.c_str());
fprintf(stderr, "remote dir=%s, remote file=%s\n", remote_dir.c_str(), remote_file.c_str());
#endif
if (m_localDir != local_dir)
{
lcd(local_dir.c_str());
m_localDir = local_dir;
}
if (m_remoteDir != remote_dir)
{
m_remoteDir = remote_dir;
unsigned int line_pos = 0;
while (1)
{
line_pos = remote_dir.find('/', line_pos);
if (line_pos != remote_dir.npos)
{
unsigned int line_pos_tmp = remote_dir.find('/', line_pos+1);
if (line_pos_tmp != remote_dir.npos)
{
string sub_str = remote_dir.substr(line_pos+1, line_pos_tmp-line_pos-1);
line_pos = line_pos_tmp;
Makedir(sub_str.c_str());
GotoDir(sub_str.c_str());
}
else
break;
}
else
break;
}
}
PassiveModeFileTransferConnect(); //开启被动模式传输
FtpPutFile(local_file.c_str(), remote_file.c_str());
if (m_recvContent.find("226") != m_recvContent.npos)
return 0;
else
{
fprintf(stderr, "file transferr error !\n");
return -1;
}
return -1;
}
int CFtpClient::Makedir(const char *dir)
{
CommonCommand("MKD %s", dir);
if (m_recvContent.find("500") != m_recvContent.npos)
CommonCommand("XMKD %s", dir);
if (m_recvContent.find("257") == m_recvContent.npos) //路径名建立
{
if (m_recvContent.find("550") == m_recvContent.npos) //已经存在
return 0;
else
return -1;
}
else
return 0;
}
int CFtpClient::GotoDir(const char *dir)
{
CommonCommand("CWD %s", dir);
if (m_recvContent.find("500") != m_recvContent.npos) //不识别指令
CommonCommand("XCWD %s", dir);
if (m_recvContent.find("250") == m_recvContent.npos)
return -1;
else
return 0; //进入目录
}
void CFtpClient::lcd(const char *dir)
{
char buf[1024] = {0};
if (!dir)
{
return;
}
if (chdir(dir) < 0)
{
fprintf(stderr, "local: %s: %s\n", dir, strerror(errno));
return;
}
if (!getcwd(buf, sizeof(buf)))
{
if (errno==ERANGE) strcpy(buf, "<too long>");
else strcpy(buf, "???");
}
//printf("Local directory now %s\n", buf);
return;
}
FILE *CFtpClient::DataConn(const char *lmode)
{
return fdopen(m_fileTransferSocket, lmode);
}
int CFtpClient::FtpPutFile(const char *localFileName, const char *remoteFileName)
{
struct stat st;
register int c, d;
FILE *volatile fin, *volatile dout = 0;
volatile long bytes = 0;
char buf[1024], *bufp;
fin = fopen(localFileName, "r");
if (fin == NULL)
{
fprintf(stderr, "local: %s: %s\n", localFileName, strerror(errno));
return -1;
}
if (fstat(fileno(fin), &st) < 0 || (st.st_mode&S_IFMT) != S_IFREG)
{
fprintf(stdout, "%s: not a plain file.\n", localFileName);
fclose(fin);
return -1;
}
//CommonCommand("%s %s", "APPE", remoteFileName);
CommonCommand("%s %s", "STOR", remoteFileName);
dout = DataConn("w");
if (dout == NULL)
{
perror("fdopen:");
return (-1);
}
while ((c = read(fileno(fin), buf, sizeof (buf))) > 0)
{
bytes += c;
for (bufp = buf; c > 0; c -= d, bufp += d)
{
if ((d = write(fileno(dout), bufp, c)) <= 0)
{
perror("write");
break;
}
}
}
fclose(fin); fclose(dout);
m_fileTransferSocket = -1;
GetFtpServerReply(0);
if (c < 0)
{
fprintf(stderr, "local: %s: %s\n", localFileName, strerror(errno));
return -1;
}
#if 0
if (bytes > 0)
printf("send byte=%ld\n", bytes);
#endif
return 0;
}
bool CFtpClient::HookupOK(void)
{
GetFtpServerReply(0);
if (m_recvContent.find("220") != m_recvContent.npos) //服务就绪
return true;
return false;
}
int CFtpClient::Deinit(void)
{
if (!m_init)
return 0;
if (m_commandRead != NULL)
{
fclose(m_commandRead);
m_commandRead = NULL;
}
if (m_commandWrite != NULL)
{
fclose(m_commandWrite);
m_commandWrite = NULL;
}
if (m_fileTransferSocket != -1)
{
close(m_fileTransferSocket);
m_fileTransferSocket = -1;
}
if (m_commandSocket != -1)
{
close(m_commandSocket);
m_commandSocket = -1;
}
m_connected = 0;
m_init = false;
m_recvContent.clear();
m_remoteDir.clear();
m_localDir.clear();
return 0;
}