FTP客户端代码解析

来源:互联网 发布:万网中文域名 编辑:程序博客网 时间:2024/06/05 01:15

FTP协议程序设计客户端分析


程序流程思路

ftp程序有两个tcp连接,一个是控制连接,一个是数据连接。控制连接负责命令的传输和应答,数据连接负责传输文件数据。用户通过客户端的用户接口输入命令,然后通过客户端协议解释后发送命令,服务器端接到命令返回应答,客户端收到后再解析处理,显示出来。

一、建立控制连接

服务器在21端口上监听,然后客户端发起连接,输入登陆用户名和密码之后就创建控制连接。

二、创建数据连接

数据连接建立有两种模式,分为主动和被动(模式在init()函数中设置):mode = PASSIVE_ON;(默认被动),

主动就是客户端发送一个port命令告诉服务器将在哪个端口上监听数据连接,然后服务器从20端口发过来连接请求,建立连接。被动模式就是客户端发送一个PASV命令,服务器收到PASV命令后将在会在一个端口上监听客户端连接,并将这个端口告诉客户端,然后客户端发起连接,建立连接。(主动和被动相对于服务器来说,主动指服务器主动连接客户端的20端口,被动指服务器在特定端口监听,等待客户端连接。)

三、ftp协议规范

ftp控制连接协议格式是以命令关键字(加空格加参数)在加\r\n的格式,每个客户端命令服务器都会产生一个3位数字的应答。数据连接每次发送或接受相同字节数的数据,直到对方关闭连接或者数据已经发送完毕。

四、客户端ftp协议命令的构造

首先有一个结构体数组,其结构体成员分别是,人机交互命令,ftp协议命令,ftp协议命令参数,命令处理函数指针。数组长度对应的是协议的个数。结构体定义:`

struct ftpcmd{char *alias;                                    /* ftp cmd alias */char *name;                                     /* ftp cmd name */char *args;                                     /* ftp cmd args */int  (*handler)(int fd, char *cmd, char *args); /* ftp cmd handler */};`

结构体数组定义:`

FTPCMD ftp_cmd[] ={{"dir", "LIST",  NULL, NULL},{"ls", "LIST",   NULL, NULL},{"get", "RETR",  "Remote file", NULL},{"put",  "STOR",  "Local file", NULL},{"mdelete","NLST","Remote files",NULL},{"mget","NLST","Remote files",NULL},{"mput", "STOR","Local files",NULL},{"cd", "CWD", "Remote directory", do_common_cmd},{"delete", "DELE", "Remote file", do_common_cmd},{"lcd",  "LCD",  NULL, do_lchdir},{"mkdir","MKD", "Remote directory", do_common_cmd},{"rmdir","RMD", "Remote directory", do_common_cmd},{"passive", "PASV", NULL, do_pasv},{"pwd",  "PWD",  NULL, do_common_cmd},{"binary", "TYPE I",  NULL, do_common_cmd},{"ascii", "TYPE A",  NULL, do_common_cmd},{"bye", "QUIT",  NULL, do_common_cmd},{"user", "USER", "Username", do_user}};`

其中list和上传下载命令有分为主动和被动模式,可以通过pasv函数改变其函数指针,然后还有本地命令如改变主动被动模式的pasv命令,其他大多数普通命令,处理函数都为一个,do_command_cmd函数。在用户输入命令时,先判断命令是否正确,然后在结构体数组中找到对应的结构体,获得ftp协议命令,如果缺少参数会让你输入参数,最后调用树立函数进行处理。

五、各主要功能函数及调用关系

1、主函数 在login()函数登陆ftp服务器后进入死循环检测用户输入命令和参数判断,最后执行ftp命令ftp_cmd[found].handler(sockfd_cmd, cmd, args);

2、login()负责处理具体的连接服务器的connect()并发送登录名和密码

3、check_right()检查用户输入命令参数格式,根据定义的结构体数组的构造命令字段对比,若匹配上则返回元素索引


主动模式

active_listen()主动监听`

int active_listen()/*9*/{int sockfd;if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1){    fprintf (stderr, "Socket error: %s\a\n", strerror (errno));    return -1;}/*set protocol address for sockfd_act_listen*/memset (&local_addr, 0, sizeof local_addr);local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = htonl (INADDR_ANY);local_addr.sin_port = 0;/*bind client's data socket to local_addr to listen data connection from ftp server*/if ((bind (sockfd, (struct sockaddr *) (&local_addr), sizeof local_addr)) == -1){    fprintf (stderr, "Bind error: %s\a\n", strerror (errno));    return -1;}if(listen(sockfd, 5)){    fprintf(stderr, "Listen error: %s\n\a", strerror(errno));    return -1;}return sockfd;}`

active_notify()主动模式下监听等待服务器连接请求,在控制连接中客户端告诉服务器在指定的端口等待连接`

int active_notify(int fd)/*14*/{int status;int sockfd_listen_act;char res_buffer[BUFSIZE];unsigned short port;char quot[4], resi[4];/* restore ip_args as the original value */memset(ip_args + ipstr_len, 0, ARGSIZE - ipstr_len);/* returns the client listen socket port */sockfd_listen_act = active_listen();if(sockfd_listen_act == -1)    return 0;                               /* listen fails */port = get_active_port(sockfd_listen_act);snprintf(quot, sizeof(quot), "%d", port/256);snprintf(resi, sizeof(resi), "%d", port%256);strcat(ip_args, quot);strcat(ip_args, ",");strcat(ip_args, resi);send_ftpcmd (fd, "PORT", ip_args);status = get_ftpcmd_status(fd, res_buffer);if( status != 200 )    /* 如果端口命令不成功 */    return 0;return sockfd_listen_act;}`

make_conn_active()主动向服务器发起数据连接`

int make_conn_active(int fd_listen)/*17*/{int sockfd;socklen_t sin_size;/* 等待ftp服务器连接过来 */if((sockfd = accept(fd_listen, (struct sockaddr *)(&local_addr), &sin_size))== -1){    fprintf(stderr, "Accept error: %s\a\n", strerror(errno));    return -1;}return sockfd;}`

被动模式

passive_notify()在被动模式向服务器发出PASV类型命令,并从服务器的响应中提取监听的端口号`

int passive_notify(int fd)/*15*/{int status;int port_pasv;char res_buffer[BUFSIZE];send_ftpcmd (fd, "PASV", NULL);status = get_ftpcmd_status(fd, res_buffer);if( status != 227 )    /* 如果PASV命令不成功 */    return 0;port_pasv = parse_port(res_buffer, strlen(res_buffer));return port_pasv;}`
make_conn_passive() 被动接受服务器的数据连接`
int make_conn_passive(int port)/*18*/{int sockfd;                 /* passive mode socket for ftp data transfer *//* create client socket for passive mode */if ((sockfd = socket (PF_INET, SOCK_STREAM, 0)) == -1){    fprintf (stderr, "Socket Error: %s\a\n", strerror (errno));    return 1;}server_addr.sin_port = htons (port);/* connect to ftp server */if (connect (sockfd, (struct sockaddr *) (&server_addr), sizeof (server_addr)) == -1){    fprintf (stderr, "Connect Error: %s\a\n", strerror (errno));    return 0;}return sockfd;

}`

之后就是文件上传和下载的相关函数,这部分省略。

控制连接的命令处理函数统一通过do_common_cmd()`

int do_common_cmd(int fd, char *cmd, char *args){char res_buffer[BUFSIZE];int status;if(strlen(args))    send_ftpcmd(fd, cmd, args);else{    send_ftpcmd(fd, cmd, NULL);    if(strcmp(cmd,"TYPE I")==0)current_type=BINARY_MODE;    if(strcmp(cmd,"TYPE A")==0)current_type=ASCII_MODE;}status = get_ftpcmd_status(fd, res_buffer);if(status == 250 || status == 257        || status == 200 || status == 230        || status == 331)    return 0;                               /* success */if(status == 221 || status == 421)          /* bye input */{    close(fd);    exit(0);}return -1;

切换主被动模式,包括对两种模式下不同处理函数注册`

int do_pasv(int fd, char *cmd, char *args){mode = !mode;if(mode){    printf("Passive mode on.\n");    ftp_cmd[0].handler = do_list_pasv;      /* LIST command */    ftp_cmd[1].handler = do_list_pasv;      /* DIR command */    ftp_cmd[2].handler = do_get_pasv;       /* GET command */    ftp_cmd[3].handler = do_put_pasv;       /* PUT command */    ftp_cmd[4].handler = do_mdel_pasv;    ftp_cmd[5].handler = do_mget_pasv;    ftp_cmd[6].handler = do_mput_pasv;}else{    printf("Passive mode off.\n");    ftp_cmd[0].handler = do_list_active;    /* LIST command */    ftp_cmd[1].handler = do_list_active;    /* DIR command */    ftp_cmd[2].handler = do_get_active;     /* GET command */    ftp_cmd[3].handler = do_put_active;     /* PUT command */    ftp_cmd[4].handler = do_mdel_active;    ftp_cmd[5].handler = do_mget_active;    ftp_cmd[6].handler = do_mput_active;}return mode;}`

ftp客户端程序巧妙的运用函数指针将用户在终端输入的命令通过结构体数组成员和处理函数联系了起来,在结构体成员的命令中抽象出标准的命令接口,方便功能扩展。

1 0