Linux c使用Socket通信实现FTP客户端程序

一、FTP 概述

文件传输协议(英文:File Transfer Protocol,缩写:FTP)是用于在网络上进行文件传输的一套标准协议,使用客户/服务器模式。它属于网络传输协议的应用层。FTP的传输有两种方式:ASCII、二进制。

 

二、传输方式

ASCII传输方式

假定用户正在拷贝的文件包含的简单ASCII码文本,如果在远程机器上运行的不是UNIX,当文件传输时ftp通常会自动地调整文件的内容以便于把文件解释成另外那台计算机存储文本文件的格式。

但是常常有这样的情况,用户正在传输的文件包含的不是文本文件,它们可能是程序,数据库,字处理文件或者压缩文件。在拷贝任何非文本文件之前,用binary 命令告诉ftp逐字拷贝。

二进制传输模式

在二进制传输中,保存文件的位序,以便原始和拷贝的是逐位一一对应的。即使目的地机器上包含位序列的文件是没意义的。例如,macintosh以二进制方式传送可执行文件到Windows系统,在对方系统上,此文件不能执行。

如在ASCII方式下传输二进制文件,即使不需要也仍会转译。这会损坏数据。(ASCII方式一般假设每一字符的第一有效位无意义,因为ASCII字符组合不使用它。如果传输二进制文件,所有的位都是重要的。)。

三、支持模式

FTP支持两种模式:Standard (PORT方式,主动方式),Passive (PASV,被动方式)。

Port模式

FTP 客户端首先和服务器的TCP 21端口建立连接,用来发送命令,客户端需要接收数据的时候在这个通道上发送PORT命令。PORT命令包含了客户端用什么端口接收数据。在传送数据的时候,服务器端通过自己的TCP 20端口连接至客户端的指定端口发送数据。FTP server必须和客户端建立一个新的连接用来传送数据。

Passive模式

建立控制通道和Standard模式类似,但建立连接后发送Pasv命令。服务器收到Pasv命令后,打开一个临时端口(端口号大于1023小于65535)并且通知客户端在这个端口上传送数据的请求,客户端连接FTP服务器此端口,然后FTP服务器将通过这个端口传送数据。

 

四、FTP 命令

 

命令

描述

ABOR

中断数据连接程序

ACCT <account>

系统特权帐号

ALLO <bytes>

为服务器上的文件存储器分配字节

APPE <filename>

添加文件到服务器同名文件

CDUP <dir path>

改变服务器上的父目录

CWD <dir path>

改变服务器上的工作目录

DELE <filename>

删除服务器上的指定文件

HELP <command>

返回指定命令信息

LIST <name>

如果是文件名列出文件信息,如果是目录则列出文件列表

MODE <mode>

传输模式(S=流模式,B=块模式,C=压缩模式)

MKD <directory>

在服务器上建立指定目录

NLST <directory>

列出指定目录内容

NOOP

无动作,除了来自服务器上的承认

PASS <password>

系统登录密码

PASV

请求服务器等待数据连接

PORT <address>

IP 地址和两字节的端口 ID

PWD

显示当前工作目录

QUIT

从 FTP 服务器上退出登录

REIN

重新初始化登录状态连接

REST <offset>

由特定偏移量重启文件传递

RETR <filename>

从服务器上找回(复制)文件

RMD <directory>

在服务器上删除指定目录

RNFR <old path>

对旧路径重命名

RNTO <new path>

对新路径重命名

SITE <params>

由服务器提供的站点特殊参数

SMNT <pathname>

挂载指定文件结构

STAT <directory>

在当前程序或目录上返回信息

STOR <filename>

储存(复制)文件到服务器上

STOU <filename>

储存文件到服务器名称上

STRU <type>

数据结构(F=文件,R=记录,P=页面)

SYST

返回服务器使用的操作系统

TYPE <data type>

数据类型(A=ASCII,E=EBCDIC,I=binary)

USER <username>>

系统登录的用户名

标准 FTP 信息如下

响应代码

解释说明

110

新文件指示器上的重启标记

120

服务器准备就绪的时间(分钟数)

125

打开数据连接,开始传输

150

打开连接

200

成功

202

命令没有执行

211

系统状态回复

212

目录状态回复

213

文件状态回复

214

帮助信息回复

215

系统类型回复

220

服务就绪

221

退出网络

225

打开数据连接

226

结束数据连接

227

进入被动模式(IP 地址、ID 端口)

230

登录因特网

250

文件行为完成

257

路径名建立

331

要求密码

332

要求帐号

350

文件行为暂停

421

服务关闭

425

无法打开数据连接

426

结束连接

450

文件不可用

451

遇到本地错误

452

磁盘空间不足

500

无效命令

501

错误参数

502

命令没有执行

503

错误指令序列

504

无效命令参数

530

未登录网络

532

存储文件需要帐号

550

文件不可用

551

不知道的页类型

552

超过存储分配

553

文件名不允许

 

五、Linux c ftp客户端编写

ftp客户端代码:

/*********************************************************************************
 *      Copyright:  (C) 2019 Wu Yujun<[email protected]>
 *                  All rights reserved.
 *
 *       Filename:  ftp_client.c
 *    Description:  This file is ftp client 
 *                 
 *        Version:  1.0.0(2019年03月16日)
 *         Author:  Wu Yujun <[email protected]>
 *      ChangeLog:  1, Release initial version on "2019年03月16日 15时29分29秒"
 *                 
 ********************************************************************************/

#include "ftp_client.h"

int g_stop = 0 ;


void print_usage(const char *program_name)
{
    printf("\n%s -- (2019.3.16)\n", program_name);
    printf(" Usage: %s -i <server_ip>/<ftp_server_hostname> -p <ftp_server_port>  [-h <use_help>]\n", program_name);
    printf("        -p --port       the port of the ftp server you want to connect\n") ;
    printf("        -i --ip         the ip address or hostname of the ftp server you want to connect\n") ;
    printf("        -h --help       the ftp_client how to use\n");
    printf("After Loin OK:\n") ;
    ftp_usage_print() ;
                                    
    return ;
}
void sighandler(int sig_num)
{
    if(sig_num == SIGUSR1)
    {
        g_stop = 1 ;
    }
}

int main(int argc, char **argv)
{
    char                *program_name ;
    int                 port = 0 ;
    int                 opt = -1 ;
    char                *ip = NULL;
    struct hostent *    hostnp ;
    int                 no_ipport ;
    char                buf[BUF_SIZE] ;
    int                 sock_fd = -1 ;
    int                 rv = 0 ;
    char                cmd[32] ;


    program_name = basename(argv[0]) ;


    const char *short_opts = "i:n:p:h";                    
    const struct option long_opts[] =   {  
            {"help", no_argument, NULL, 'h'},    
            {"ip", required_argument, NULL, 'i'},
            { "port", required_argument, NULL, 'p'},  
            {0, 0, 0, 0} 
        };  
    while ((opt= getopt_long(argc, argv, short_opts, long_opts,NULL)) != -1) 
    {
        switch (opt) 
        {
            case 'i':
                ip = optarg ;
                break ;
            case 'p':
                port = atoi(optarg) ;
                break ;
            case 'h':
                print_usage(program_name) ;
                return 0 ;
        }
     }
    
    no_ipport = ( (!ip) || (!port) ) ;            //port or ip is NULL, no_ipprot = 1 ;
    if( no_ipport )
    {    
        print_usage(program_name);
        return 0;
    }

    /*  connect server get host by name    */
    if ( inet_addr(ip)== INADDR_NONE )
    {
        if( (hostnp = gethostbyname(ip) ) == NULL )
        {
            printf("get host by name failure: %s\n", strerror(h_errno)) ;
            return -1 ;
        }
        ip = inet_ntoa( * (struct in_addr *)hostnp->h_addr );
    }

    signal(SIGUSR1, sighandler);


    if( (sock_fd=connect_server(ip, port)) < 0 )
    {
        printf("connect_server() failed\n") ;
        return -2 ;
    }

    memset(buf, 0, sizeof(buf)) ;
    if( read(sock_fd, buf, sizeof(buf)) <= 0)
    {
        printf("Read information from ftp_server failed:%s\n", strerror(errno)) ;
        goto cleanup ;
    }
    printf("ftp Information: %s\n", buf) ;
   
    while(rv<=0)
    {   
        rv = login(sock_fd) ;
    }
    

    while(!g_stop)
    {
        memset(cmd, 0, sizeof(cmd)) ;
        printf("\nFTP: ") ;
        scanf("%s", cmd) ;
        if(!strcmp(cmd,"ls"))
        {
            if( ftp_list(sock_fd) == ERROR )
                continue ;
        }
        else if(!strcmp(cmd,"get"))
        {
            if( ftp_get(sock_fd) == ERROR)
                continue ;
        }
        else if(!strcmp(cmd,"put"))
        {
            if( ftp_put(sock_fd) ==ERROR)
            {
                continue ;
            }
            read(sock_fd, buf, sizeof(buf));
            printf("Read from ftp:%s\n",buf) ;
        }
        else if(!strcmp(cmd,"quit"))
        {
            write(sock_fd, "quit\r\n", strlen("quit\r\n")) ;
            read(sock_fd, buf, sizeof(buf)) ;
            printf("Read from ftp: %s",buf) ;
            goto cleanup ;
        }
        else if(!strcmp(cmd,"cd"))
        {
            ftp_cwd(sock_fd) ;
        }
        else{
            ftp_usage_print() ;
            continue ;
        }

    }

cleanup:
    printf("program %s is stop\n", program_name) ;
    close(sock_fd) ;
    return 0 ;
/*  End Of main */
}

 

登录代码

给ftp server控制端发送“USER (username)\r\n”,如果用户名正确会接收到331 Please specify the password.\r\n接着就可以发送密码进行登录,PASS (password)\r\n,如果登录成功会接收到”230 Login successful.\r\n“,getpass()函数输入不回显;

#include "ftp_client.h"

int login(int sock_fd)

{

    int         rv = 0 ;

    char        buf[256] ;

    char        scanf_buf[256] ;

    char        *password ;





    printf("\n/************ START LOGIN *************/\n") ;

    /*  Input ftp username  */

    printf("Please input User_name:") ;

    memset(scanf_buf, 0, sizeof(scanf_buf)) ;

    scanf("%s",scanf_buf) ;

    memset(buf, 0 ,sizeof(buf)) ;

    snprintf(buf, sizeof(buf),"USER %s\r\n",scanf_buf) ;

    if(write(sock_fd, buf, strlen(buf))< 0 )

    {

        printf("write to ftp server failed:%s\n",strerror(errno)) ;

        return ERROR ;

    }

    memset(buf, 0, sizeof(buf)) ;

    if( (rv =read(sock_fd, buf, sizeof(buf)) ) <= 0)

    {

        printf("Read from ftp_server failed:%s\n", strerror(errno)) ;

        return ERROR ;

    }

    //printf("Read %d from ftp: %s\n",rv, buf) ;

    if(strcmp(buf,"331 Please specify the password.\r\n")!= 0)

    {

     printf("Invaild user name\n") ;

return ERROR ;

    }



    /*  input ftp user passwd     */

    password = getpass("Please input Password:");

    memset(buf, 0,sizeof(buf)) ;

    snprintf(buf,sizeof(buf),"PASS %s\r\n",password);

    if(write(sock_fd, buf, strlen(buf))< 0 )

    {         

        printf("write to ftp server failed:%s\n",strerror(errno)) ;

        return ERROR ;

    }          

    if( (rv = read(sock_fd, buf, sizeof(buf))) <= 0)

    {

        printf("Read from ftp_server failed:%s\n", strerror(errno)) ;

        return ERROR ;

    }

    //printf("Read %d byte from ftp: %s\n",rv, buf) ;

    if(strcmp(buf,"230 Login successful.\r\n")== 0)

    {

        printf("You are Welcome!\n") ;

        printf("/************ FINISH LOGIN *************/\n") ;

        return TRUE ;

    }

    else 

    {

        printf("Invaild username or password, Please try again\n") ;

        return ERROR ;

    }

}

 

 

主动模式(port)需要客户端有公网ip,port模式提供的ip地址和port端口ftp服务器才能连接得上,被动模式(pasv)ftp服务器开一个临时端口并发送ip地址和port端口,客户端连接上就能进行数据传输。通常客户端不会有公网ip,所以我的代码都是用被动模式(pasv)实现的,如果ftp服务器也没有公网ip那么需要在局域网下面进行操作,被动模式发来的 (h1,h2,h3,h4,p1,p2),h1.h2.h3.h4是ip地址,p1*256+p2是port端口。

被动模式代码

#include "ftp_client.h"

int ftp_pasv(int sock_fd)

{

    char    pasv_ip[64] ;

    char    ip0[4],ip1[4],ip2[4],ip3[4] ;

    char    port0[4], port1[4] ;

    int     port ;

    char    ip[32] ;

    int     pasv_fd = -1 ;



    char    *ip_start = NULL, *ip_end =NULL ;

    if( write(sock_fd, "PASV\r\n", strlen("PASV\r\n")) <= 0 )  //Send PASV to tell ftp server start PASV mode

    {

        printf("write to ftp failed: %s\n", strerror(errno)) ;

        return ERROR ;

    }

    memset(pasv_ip, 0, sizeof(pasv_ip)) ;

    read(sock_fd, pasv_ip, sizeof(pasv_ip)) ;//ftp will send “227 Entering Passive Mode (xxx,xxx,xxx,xxx,xxx,xxx).”

    //printf("%s\n", pasv_ip) ;



    ip_start = strstr(pasv_ip, "(") ;

    ip_start ++ ;   

    ip_end = strstr(ip_start, ",") ;

    memset(ip0, 0, sizeof(ip0)) ;

    strncpy(ip0, ip_start, (ip_end - ip_start)) ;



    ip_start = ip_end + 1 ;

    ip_end = strstr(ip_start, ",") ;

    memset(ip1, 0, sizeof(ip1)) ;

    strncpy(ip1, ip_start, (ip_end-ip_start));



    ip_start = ip_end + 1 ;

    ip_end = strstr(ip_start, ",") ;

    memset(ip2, 0, sizeof(ip2)) ;

    strncpy(ip2, ip_start, (ip_end-ip_start)) ;

    

    ip_start = ip_end + 1 ;

    ip_end = strstr(ip_start, ",") ;

    memset(ip3, 0, sizeof(ip3)) ;

    strncpy(ip3, ip_start, (ip_end-ip_start)) ;



    snprintf(ip, sizeof(ip),"%s.%s.%s.%s\n", ip0,ip1,ip2,ip3) ;//analysis ip address



    ip_start = ip_end + 1 ;

    ip_end = strstr(ip_start, ",") ;

    memset(port0, 0, sizeof(port0)) ;

    strncpy(port0, ip_start, (ip_end-ip_start)) ;

    

    ip_start = ip_end+1 ;

    ip_end = strstr(ip_start, ")") ;

    memset(port1, 0, sizeof(port1)) ;

    strncpy(port1, ip_start, ip_end- ip_start) ;

    port = atoi(port0)*256+atoi(port1) ;//analysis port



    pasv_fd = connect_server(ip, port) ;//connect ftp data port

    if(pasv_fd < 0)

    {

        printf("connect ftp pasv fail!\n") ;

        return ERROR ;

    }

    return pasv_fd ;

}

接下来的其他功能都是登录上之后,控制端(sock_fd)给ftp服务器发送PASV,创建一个数据端(pasv_fd)的连接,接着控制端(sock_fd)发送你想要的操作,数据端(pasv_fd)负责接收就完成了,源代码:https://gitee.com/wyj98/ftp_client/

 

使用命令:./ftp -i ftp服务器的ip地址或域名 -p ftp服务器的端口

一开始需要登录,登录成功后输入help可以查看ftp_client功能

ls列出ftp服务器的文件

输入get下载,接着输入需要下载的文件名,下载成功后quit退出

输入put进行上传,然后输入需要上传的文件名字

上传成功

六、编写过程中出现的问题

给ftp服务器写控制命令之后,如果不及时将ftp服务器发回来的消息读出来,大概是不及时将数据从receive buffer中取出,最终导致receive buffer填满,接收端会阻止发送端向其发送数据。这些控制皆发生在TCP/IP栈中,对应用程序是透明的,应用程序继续发送数据,最终导致send buffer填满,write调用阻塞。会出现段错误。

调试过程中发现对图片上传和下载会出错,一直以为是传输模式的问题,我调用的write最后一个参数传的是strlen(buf),strlen所作的仅仅是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,最后解决方法是rv=read(), write()最后一个参数传的rv,read返回值为实际读取到的字节数,如果返回0,表示已到达文件尾或无可读取的数据。

 

参考:https://www.ibm.com/developerworks/cn/linux/l-cn-socketftp/index.html#FTP

 

猜你喜欢

转载自blog.csdn.net/caijiwyj/article/details/88699562
今日推荐