關於FTP(未完)

一、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: [email protected]
 */

#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: [email protected]
 */

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章