使用 Socket 通信实现 FTP 客户端程序
FTP 概述
文件传输协议(FTP)作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用。FTP的目标是提高文件的共享性和可靠高效地传送数据。
在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据。本文主要基于 LINUX 平台,对 FTP 客户端的实现原理进行详尽的解释并阐述如何使用 C 语言编写一个简单的 FTP 客户端。
FTP 协议
相比其他协议,如 HTTP 协议,FTP 协议要复杂一些。与一般的 C/S 应用不同点在于一般的C/S 应用程序一般只会建立一个 Socket 连接,这个连接同时处理服务器端和客户端的连接命令和数据传输。而FTP协议中将命令与数据分开传送的方法提高了效率。
FTP 使用 2 个端口,一个数据端口和一个命令端口(也叫做控制端口)。这两个端口一般是21 (命令端口)和 20 (数据端口)。控制 Socket 用来传送命令,数据 Socket 是用于传送数据。每一个 FTP 命令发送之后,FTP 服务器都会返回一个字符串,其中包括一个响应代码和一些说明信息。其中的返回码主要是用于判断命令是否被成功执行了。
命令端口
一般来说,客户端有一个 Socket 用来连接 FTP 服务器的相关端口,它负责 FTP 命令的发送和接收返回的响应信息。一些操作如“登录”、“改变目录”、“删除文件”,依靠这个连接发送命令就可完成。
数据端口
对于有数据传输的操作,主要是显示目录列表,上传、下载文件,我们需要依靠另一个 Socket来完成。
如果使用被动模式,通常服务器端会返回一个端口号。客户端需要用另开一个 Socket 来连接这个端口,然后我们可根据操作来发送命令,数据会通过新开的一个端口传输。
如果使用主动模式,通常客户端会发送一个端口号给服务器端,并在这个端口监听。服务器需要连接到客户端开启的这个数据端口,并进行数据的传输。
下面对 FTP 的主动模式和被动模式做一个简单的介绍。
主动模式 (PORT)
主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出 “port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。
FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。
被动模式 (PASV)
为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。
被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。
第一个端口连接服务器的 21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 开头的信息,在括号中有以逗号隔开的六个数字,前四个指服务器的地址,最后两个,将倒数第二个乘 256 再加上最后一个数字,这就是 FTP 服务器开放的用来进行数据传输的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口号是 p1*256+p2,ip 地址为h1.h2.h3.h4。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。
主要用到的 FTP 命令
FTP 每个命令都有 3 到 4 个字母组成,命令后面跟参数,用空格分开。每个命令都以 "\r\n"结束。
要下载或上传一个文件,首先要登入 FTP 服务器,然后发送命令,最后退出。这个过程中,主要用到的命令有 USER、PASS、SIZE、REST、CWD、RETR、PASV、PORT、QUIT。
USER: 指定用户名。通常是控制连接后第一个发出的命令。“USER gaoleyi\r\n”: 用户名为gaoleyi 登录。
PASS: 指定用户密码。该命令紧跟 USER 命令后。“PASS gaoleyi\r\n”:密码为 gaoleyi。
SIZE: 从服务器上返回指定文件的大小。“SIZE file.txt\r\n”:如果 file.txt 文件存在,则返回该文件的大小。
CWD: 改变工作目录。如:“CWD dirname\r\n”。
PASV: 让服务器在数据端口监听,进入被动模式。如:“PASV\r\n”。
PORT: 告诉 FTP 服务器客户端监听的端口号,让 FTP 服务器采用主动模式连接客户端。如:“PORT h1,h2,h3,h4,p1,p2”。
RETR: 下载文件。“RETR file.txt \r\n”:下载文件 file.txt。
STOR: 上传文件。“STOR file.txt\r\n”:上传文件 file.txt。
REST: 该命令并不传送文件,而是略过指定点后的数据。此命令后应该跟其它要求文件传输的 FTP 命令。“REST 100\r\n”:重新指定文件传送的偏移量为 100 字节。
QUIT: 关闭与服务器的连接。
FTP 响应码
客户端发送 FTP 命令后,服务器返回响应码。
响应码用三位数字编码表示:
第一个数字给出了命令状态的一般性指示,比如响应成功、失败或不完整。
第二个数字是响应类型的分类,如 2 代表跟连接有关的响应,3 代表用户认证。
第三个数字提供了更加详细的信息。
第一个数字的含义如下:
1 表示服务器正确接收信息,还未处理。
2 表示服务器已经正确处理信息。
3 表示服务器正确接收信息,正在处理。
4 表示信息暂时错误。
5 表示信息永久错误。
第二个数字的含义如下:
0 表示语法。
1 表示系统状态和信息。
2 表示连接状态。
3 表示与用户认证有关的信息。
4 表示未定义。
5 表示与文件系统有关的信息。
Socket 编程的几个重要步骤
Socket 客户端编程主要步骤如下:
- socket() 创建一个 Socket
- connect() 与服务器连接
- write() 和 read() 进行会话
- close() 关闭 Socket
Socket 服务器端编程主要步骤如下:
- socket() 创建一个 Socket
- bind()
- listen() 监听
- accept() 接收连接的请求
- write() 和 read() 进行会话
- close() 关闭 Socket
实现 FTP 客户端上传下载功能
下面让我们通过一个例子来对 FTP 客户端有一个深入的了解。本文实现的 FTP 客户端有下列功能:
- 客户端和 FTP 服务器建立 Socket 连接。
- 向服务器发送 USER、PASS 命令登录 FTP 服务器。
- 使用 PASV 命令得到服务器监听的端口号,建立数据连接。
- 使用 RETR/STOR 命令下载/上传文件。
- 在下载完毕后断开数据连接并发送 QUIT 命令退出。
本例中使用的 FTP 服务器为 filezilla。在整个交互的过程中,控制连接始终处于连接的状态,数据连接在每传输一个文件时先打开,后关闭。
客户端和 FTP 服务器建立 Socket 连接
当客户端与服务器建立连接后,服务器会返回 220 的响应码和一些欢迎信息。
图 1. 客户端连接到服务器端
清单 1. 客户端连接到 FTP 服务器,接收欢迎信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
客户端登录 FTP 服务器
当客户端发送用户名和密码,服务器验证通过后,会返回 230 的响应码。然后客户端就可以向服务器端发送命令了。
图 2. 客户端登录 FTP 服务器
清单 2. 客户端发送用户名和密码,登入 FTP 服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
客户端让 FTP 服务器进入被动模式
当客户端在下载/上传文件前,要先发送命令让服务器进入被动模式。服务器会打开数据端口并监听。并返回响应码 227 和数据连接的端口号。
图 3. 客户端让服务器进入被动模式
清单 3. 让服务器进入被动模式,在数据端口监听
1 2 3 4 5 6 7 |
|
客户端通过被动模式下载文件
当客户端发送命令下载文件。服务器会返回响应码 150,并向数据连接发送文件内容。
图 4. 客户端从FTP服务器端下载文件
清单 4. 客户端连接到 FTP 服务器的数据端口并下载文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
客户端退出服务器
当客户端下载完毕后,发送命令退出服务器,并关闭连接。服务器会返回响应码 200。
图 5. 客户端从 FTP 服务器退出
清单 5. 客户端关闭数据连接,退出 FTP 服务器并关闭控制连接
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
至此,下载文件已经完成。需要注意的是发送 FTP 命令的时候,在命令后要紧跟 “\r\n”,否则服务器不会返回信息。回车换行符号 “\r\n” 是 FTP 命令的结尾符号,当服务器接收到这个符号时,认为客户端发送的命令已经结束,开始处理。否则会继续等待。
让我们来看一下 FTP 服务器这一端的响应情况:
清单 6. 客户端下载文件时,FTP 服务器的响应输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
首先,服务器准备就绪后返回 220。客户端接收到服务器端返回的响应码后,相继发送“USER username” 和 “PASS password” 命令登录。随后,服务器返回的响应码为 230 开头,说明客户端已经登入了。这时,客户端发送 PASV 命令让服务器进入被动模式。服务器返回如 “227 Entering Passive Mode (127,0,0,1,13,67)”,客户端从中得到端口号,然后连接到服务器的数据端口。接下来,客户端发送下载命令,服务器会返回响应码 150,并从数据端口发送数据。最后,服务器返回 “226 transfer complete”,表明数据传输完成。
需要注意的是,客户端不要一次发送多条命令,例如我们要打开一个目录并且显示这个目录,我们得发送 CWD dirname,PASV,LIST。在发送完 CWD dirname 之后等待响应代码,然后再发送后面一条。当 PASV 返回之后,我们打开另一个 Socket 连接到相关端口上。然后发送 LIST,返回 125 之后在开始接收数据,最后返回 226 表明完成。
在传输多个文件的过程中,需要注意的是每次新的传输都必须重新使用 PASV 获取新的端口号,接收完数据后应该关闭该数据连接,这样服务器才会返回一个 2XX 成功的响应。然后客户端可以继续下一个文件的传输。
上传文件与下载文件相比,登入验证和切换被动模式都如出一辙,只需要改变发送到服务器端的命令,并通过数据连接发送文件内容。
客户端通过被动模式向服务器上传文件
当客户端发送命令上传文件,服务器会从数据连接接收文件。
图 6. 客户端连接到 FTP 服务器的数据端口并上传文件
客户端通过主动模式向服务器上传文件
到目前为止,本文介绍的都是客户端用被动模式进行文件的上传和下载。下面将介绍客户端用主动模式下载文件。
图 7. 用主动模式从 FTP 服务器下载文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
客户端通过 PORT 命令告诉服务器连接自己的 p1*256+p2 端口。随后在这个端口进行监听,等待 FTP 服务器连接上来, 再通过这个数据端口来传输文件。PORT 方式在传送数据时,FTP 客户端其实就相当于一个服务器端,由 FTP 服务器主动连接自己。
断点续传
由于网络不稳定,在传输文件的过程中,可能会发生连接断开的情况,这时候需要客户端支持断点续传的功能,下次能够从上次终止的地方开始接着传送。需要使用命令 REST。如果在断开连接前,一个文件已经传输了 512 个字节。则断点续传开始的位置为 512,服务器会跳过传输文件的前 512 字节。
清单 8. 从 FTP 服务器断点续传下载文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
结束语
本文从应用实现的角度,介绍了 FTP 协议。并用详尽的例子分析了如何用主动模式和被动模式实现 FTP 客户端上传下载文件,如何进行断点续传。通过本文可以让读者对 FTP 客户端的原理有一个深入的了解。
这里借用轻飘飞扬博主的代码测试通过
#include<stdio.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<netinet/in.h>
#include<netdb.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define SERV_PORT 21
#define MAXSIZE 1024
#define SA struct sockaddr
static int control_sockfd;
int npsupport;
int login_yes;
int f;//f=0时为默认文件结构
int login();
void ftp_list(int control_sockfd);
void zeromery(char *a,int len);
void ftp_pwd(int control_sockfd);
void ftp_changdir(char dir[],int control_sockfd);
void ftp_quit(int control_sockfd);
void ftp_creat_mkd(char *path,int control_sockfd);
void ftp_back(int control_sockfd);
void ftp_stru(int control_sockfd);
void ftp_rest(int control_sockfd);
int ftp_download(int control_sockfd);
char *itoa(int value, char *string, int radix);
int main(int argc,char **argv)
{
printf("ftp>");
char command[MAXSIZE];
char*cmd;
scanf("%s",command);
cmd=command;
while(*(cmd)==' ')
cmd++;
if(strncmp(cmd,"login",5)==0)
{
login();
if(login_yes==1)
{
while(1)
{
comm:
sleep(1);
printf("ftp>");
zeromery(command,1024);
scanf("%s",command);
cmd=command;
while(*(cmd)==' ')
cmd++;
if(strncmp(cmd,"pasv",4)==0)
{
ftp_list(control_sockfd);
}
if(strncmp(cmd,"port",4)==0)
{
ftp_list(control_sockfd);
}
if(strncmp(cmd,"list",4)==0)
{
ftp_pwd(control_sockfd);
ftp_list(control_sockfd);
}
if(strncmp(cmd,"pwd",3)==0)
{
ftp_pwd(control_sockfd);
}
if(strncmp(cmd,"mkdir",5)==0)
{
char path[60];
zeromery(path,60);
printf("创建的路径名: ");
scanf("%s",path);
printf("s/n",path);
ftp_creat_mkd(path,control_sockfd);
}
if(strncmp(cmd,"back",4)==0)
{
ftp_back(control_sockfd);
ftp_pwd(control_sockfd);
}
if(strncmp(cmd,"cd",2)==0)
{
int i;
char path[60];
zeromery(path,60);
printf("要到的路径:");
scanf("%s",path);
printf("%s/n",path);
ftp_changdir(path,control_sockfd);
}
if(strncmp(cmd,"get",3)==0)
{
ftp_pwd(control_sockfd);
ftp_download(control_sockfd);
}
if(strncmp(cmd,"up",3)==0)
{
ftp_pwd(control_sockfd);
ftp_up(control_sockfd);
}
if(strncmp(cmd,"quit",4)==0)
{
printf("bye^_^/n");
close(control_sockfd);
break;
}
printf("支持 list,pwd,mkdir,back,cd,up,get/n");
}
}
else if(login_yes==0)
{
int i;//不成功登录下最多还有两次机会,如果不能在两次登录,则,关闭链接。
printf("Can not login vsftpd");
for(i=2;i>0;i--)
{
printf("你还有 %d 登录机会/n",i);
login();
if(login_yes==1)
{
goto comm;
}
}
if(i==0)
{
printf("你不能在登录!/n");
close(control_sockfd);
}
}
else if (strncmp(cmd,"quit",4)==0)
{
ftp_quit(control_sockfd);
close(control_sockfd);
}
}
return 0;
}
int login()
{
//初始化端口信息
struct sockaddr_in serv_addr;
char senddate,recvdate;
char sendline[MAXSIZE],recvline[MAXSIZE];
struct hostent *host;
//获取hostent中相关参数
char name[MAXSIZE],password[MAXSIZE];
printf("please enter the hostname/n");
printf("ftp-> ");
scanf("%s",name);
host=gethostbyname(name);
if(host==NULL)
{
printf("get host by name is error!/n");
login_yes=0;
}
else
{
//创建socket
control_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(control_sockfd<0)
{
printf("socket is error/n");
login_yes=0;
}
//设置sockaddr_in 结构体中的相关参数
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
serv_addr.sin_addr.s_addr=INADDR_ANY;
//调用connect函数发起连接
char addr[MAXSIZE];
if((connect(control_sockfd,(SA*)&serv_addr,sizeof(serv_addr)))<0)
{
printf("connect is error/n");
login_yes=0;
}
printf("connect to %s/n",inet_ntop(AF_INET,host->h_addr,addr,1024));
recvdate=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvdate==-1)
{
printf("recvdate is connect error/n");
login_yes=0;
}
else if(strncmp(recvline,"220",3)==0)
{
printf("connect success,pelase enter username/n");
login_yes=1;
}
else
{
printf("220 connect is error!");
login_yes=0;
}
//ftp用户登录主体部分
int sendbytes,recvbytes;
zeromery(name,1024);
zeromery(password,1024);
zeromery(recvline,1024);
zeromery(sendline,1024);
printf("ftp-> ");
scanf("%s",name);//可以支持匿名登录vsftpd
strcat(sendline,"USER ");
strcat(sendline,name);
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes==-1)
{
printf("send is wrong/n");
login_yes=0;
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"331",3)==0)
{
printf("331 please specify the password./n");
}
else
{
printf("recv date is error./n");
login_yes=0;
}
zeromery(sendline,1024);
zeromery(recvline,1024);
printf("ftp-> ");
scanf("%s",password);
strcat(sendline,"PASS ");
strcat(sendline,password);
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes==-1)
{
printf("pass send is error/n");
login_yes=0;
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"230",3)==0)
{
printf("login success!/n");
login_yes=1;
}
else
{
printf("pass recv is error/n");
login_yes=0;
}
//支持断点续传
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"REST ");
strcat(sendline,"0");
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes==-1)
{
printf("rest send is error!/n");
login_yes=0;
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes==-1)
{
printf("rest recv date is error./n");
login_yes=0;
}
if(strncmp(recvline,"350 Restart position accepted (0).",34)==0)
{
npsupport=1;
printf("support 断点续传/n");
login_yes=1;
}
else
{
npsupport=0;
printf("not support 断点续传/n");
login_yes=0;
}
//获取服务器版本信息
zeromery(recvline,1024);
zeromery(sendline,1024);
strcat(sendline,"SYST");
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes==-1)
{
printf("syst send is error/n");
login_yes=0;
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes==-1)
{
printf("syst recv is error/n");
login_yes=0;
}
if(strncmp(recvline,"215 UNIX Type: L8",17)==0)
{
printf("%s",recvline);
login_yes=1;
}
else
{
printf("syst recv connectin is error/n");
login_yes=0;
}
}
return login_yes;
}
//数组初始化
void zeromery(char *a,int len)
{
int i;
len=sizeof(a);
for(i=0;i<len;i++)
{
a[i]=0;
}
}
//quit函数,control_sockfd,通过实参传递
void ftp_quit(int control_sockfd )
{
char sendline[1024];
char recvline[1024];
int recvbytes;
int sendbytes;
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"QUIT");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("quit send is error!/n");
exit(1);
}
recvbytes=recv(control_sockfd,recvline,strlen(recvline),0);
if(strncmp(recvline,"221",3)==0)
{
printf("221 bye!^_^");
exit(1);
}
else
{
printf("quit recv is error!/n");
exit(1);
}
}
//mkd,在所在路径中创建目录 函数
void ftp_creat_mkd(char *path,int control_sockfd)
{
char sendline[1024];
char recvline[1024];
zeromery(sendline,1024);
zeromery(recvline,1024);
int recvbytes,sendbytes;
int issuccess;
strcat(sendline,"MKD ");
strcat(sendline,path);
strcat(sendline,"/r/n");
printf("%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("mkd send is error!");
exit(1);
}
recvbytes=recv(control_sockfd,recvline,strlen(recvline),0);
if(strncmp(recvline,"257",3)==0)
{
issuccess=1;
}
else
{
issuccess=0;
}
}
//改变目录函数chdir
void ftp_changdir(char *dir,int control_sockfd)
{
char sendline[1024];
char recvline[1024];
int recvbytes,sendbytes;
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"CWD ");
strcat(sendline,dir);
strcat(sendline,"/r/n");
printf("%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("cwd send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("cwd recv is error!/n");
}
if(strncmp(recvline,"250",3)==0)
{
char buf[55];
snprintf(buf,39,">>> %s/n",recvline);
printf("%s/n",buf);
}
else
{
printf("cwd chdir is error!/n");
exit(1);
}
}
//pwd 命令函数
//在应答中返回当前工作目录,“pwd”+/r/n
void ftp_pwd(int control_sockfd)
{
int recvbytes,sendbytes;
char sendline[1024],recvline[1024];
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"PWD");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("pwd,send is error/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"257",3)==0)
{
int i=0;
char *ptr;
char currendir[1024];
zeromery(currendir,1024);
ptr=recvline+5;
while(*(ptr)!='"')
{
currendir[i++]=*(ptr);
ptr++;
}
currendir[i]='/0';
printf("current directory is:%s/n",currendir);
}
else
{
printf("pwd,recv is error!/n");
}
}
//获取服务器文件列表
//list命令,是数据通道,通过的是21端口。the function 's struct is "the data //transport mode"(ascii or b) puls "the data mode to transport"(pasv or port//) puls "the list command"
void ftp_list(int control_sockfd)
{
int pasv_or_port;// 定义the ftp协议的两种不同工作mode
int recvbytes,sendbytes;
char sendline[1024],recvline[1024];
struct sockaddr_in serv_addr;
int i,j;
int flag=0;
int data_sockfd;
//用户来选择pasv 或者是 port mode(默认的是pasv模式)
char selectdata_mode_tran[1024];
zeromery(selectdata_mode_tran,1024);
zeromery(sendline,1024);
zeromery(recvline,1024);
//printf("ftp->ftp协议工作方式选择(pasv or port)/n");
//printf("ftp->");
// scanf("%s",selectdata_mode_tran);
//if(strncmp(selectdata_mode_tran,"pasv",4)==0)
//{
pasv_or_port=0;
// }
// if(strncmp(selectdata_mode_tran,"port",4)==0)
// {
// pasv_or_port=1;
// }
//pasv mode
if(pasv_or_port==0)
{
strcat(sendline,"PASV");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("pasv send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("pasv recv is error!/n");
}
if(strncmp(recvline,"227",3)==0)
{
printf("%s/n",recvline);
}
else
{
printf("pasv recv is error!/n");
}
//处理ftp server 端口
char *ptr1,*ptr2;
char num[1024];
zeromery(num,1024);
//取低位字节
ptr1=recvline+strlen(recvline);
while(*(ptr1)!=')')
{
ptr1--;
}
ptr2=ptr1;
while(*(ptr2)!=',')
ptr2--;
strncpy(num,ptr2+1,ptr1-ptr2-1);
i=atoi(num);//将字符串转换成整数
//取高位字节
zeromery(num,1024);
ptr1=ptr2;
ptr2--;
while(*(ptr2)!=',')
ptr2--;
strncpy(num,ptr2+1,ptr1-ptr2-1);
j=atoi(num);
//初始化服务器数据连接时的端口信息
int data_serviceport;
data_serviceport=j*256+i;
data_sockfd=socket(AF_INET,SOCK_STREAM,0);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=INADDR_ANY;
serv_addr.sin_port=htons(data_serviceport);
if(connect(data_sockfd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr))==-1)
{
printf("pasv data connect is error!/n");
}
}
//port mode
if(pasv_or_port==1)
{
data_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(data_sockfd<0)
{
printf("创建数据端口连接失败!/n");
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=INADDR_ANY;
serv_addr.sin_port=htons(SERV_PORT);
int ret;
int addrlen;
ret=sizeof(struct sockaddr_in);
getsockname(data_sockfd,(SA*)&serv_addr,&ret);
//处理port 后面要带的参数
char ip[1024];
int i,j;
char data[1024];
zeromery(ip,1024);
zeromery(data,1024);
inet_ntop(AF_INET,&(serv_addr.sin_addr),ip,sizeof(ip));
printf("%s/n",ip);
i=data_sockfd/256;
j=data_sockfd%256;
//将点分十进制的点转换为逗号。
char *ptr1;
ptr1=ip;
while(*(ptr1)!='/0')
{
if(*(ptr1)=='.')
{
*(ptr1)=',';
}
ptr1++;
}
strcat(sendline,"PORT ");
strcat(sendline,ip);
strcat(sendline,",");
strcat(sendline,itoa(i,data,10));
strcat(sendline,",");
strcat(sendline,itoa(j,data,10));
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("port send is error!/n");
exit(1);
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"200",3)==0)
{
printf("%s/n",recvline);
}
else
{
printf("port recv is error!/n");
}
}
//type
zeromery(recvline,1024);
zeromery(sendline,1024);
strcat(sendline,"TYPE ");
strcat(sendline,"I");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf(" type send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"200",3)==0)
{
printf("使用二进制传输数据/n");
}
else
{
printf("type recv is error!/n");
}
//list
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"LIST");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("list send is error!/n");
}
recvdata:
sleep(1);
recvbytes=recv(data_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
close(data_sockfd);
goto ending;
}
printf("%s",recvline);
if(flag==0)
{
zeromery(recvline,1024);
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"226",3)!=0)
{
flag=1;
goto recvdata;
}
}
ending:
if(flag!=1)
{
zeromery(recvline,1024);
}
close(data_sockfd);
}
//itoa 函数的实现(整数转换成字符串)
char *itoa(int value, char *string, int radix)
{
char tmp[33];
char *tp = tmp;
int i;
unsigned v;
int sign;
char *sp;
sign = (radix == 10 && value < 0);
if (sign)
v = -value;
else
v = (unsigned)value;
while (v || tp == tmp)
{
i = v % radix;
v = v / radix;
if (i < 10)
*tp++ = i+'0';
else
*tp++ = i + 'a' - 10;
}
if (string == 0)
string = (char *)malloc((tp-tmp)+sign+1);
sp = string;
if (sign)
*sp++ = '-';
while (tp > tmp)
*sp++ = *--tp;
*sp = 0;
return string;
}
//back 返回上一级函数,相当于cd /;
void ftp_back(int control_sockfd)
{
char sendline[1024],recvline[1024];
int recvbytes,sendbytes;
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"CDUP");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("cdup send is error !/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("cdup recv is error !/n");
}
if(strncmp(recvline,"250",3)==0)
{
printf("请求的文件操作已经成功/n");
}
}
//stru命令的实现
void ftp_stru(int control_sockfd)
{
int recvbytes,sendbytes;
char sendline[1024],recvline[1024];
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"STRU");
strcat(sendline,"F");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("stru send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("stru recv is error!/n");
}
if(strncmp(recvline,"200",3)==0)
{
f=0;
}
}
//断点函数的支持
void ftp_rest(int control_sockfd)
{
int recvbytes,sendbytes;
char sendline[1024],recvline[1024];
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"REST ");
strcat(sendline,"500");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("stru send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("stru recv is error!/n");
}
if(strncmp(recvline,"350",3)==0)
{
printf("%s/n",recvline);
}
}
//下载的实现函数
int ftp_download(int control_sockfd)
{
int pasv_or_port;// 定义the ftp协议的两种不同工作mode
int recvbytes,sendbytes;
char sendline[1024],recvline[1024];
struct sockaddr_in serv_addr;
FILE *fd;
int i,j;
int data_sockfd;
//rest
ftp_rest(control_sockfd);
//type
zeromery(recvline,1024);
zeromery(sendline,1024);
strcat(sendline,"TYPE ");
strcat(sendline,"I");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf(" type send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"200",3)==0)
{
printf("使用二进制传输数据/n");
}
else
{
printf("type recv is error!/n");
}
if(npsupport==1)
{
//open the file
int size;
char localpathname[60];//预打开的文件路径字符串
int flags;
char pathname[60];
unsigned int mode;
//用户来选择pasv 或者是 port mode
char selectdata_mode_tran[1024];
zeromery(selectdata_mode_tran,1024);
zeromery(sendline,1024);
zeromery(recvline,1024);
pasv_or_port=0;//(默认是pasv模式)
//pasv mode
if(pasv_or_port==0)
{
strcat(sendline,"PASV");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("pasv send is error!/n");
}
zeromery(recvline,1024);
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("pasv recv is error!/n");
}
if(strncmp(recvline,"227",3)==0)
{
char buf[55];
snprintf(buf,51,">>> %s/n",recvline);
printf("%s/n",buf);
}
else
{
printf("pasv recv is error!/n");
}
//处理ftp server 端口
char *ptr1,*ptr2;
char num[1024];
zeromery(num,1024);
//取低位字节
ptr1=recvline+strlen(recvline);
while(*(ptr1)!=')')
{
ptr1--;
}
ptr2=ptr1;
while(*(ptr2)!=',')
ptr2--;
strncpy(num,ptr2+1,ptr1-ptr2-1);
i=atoi(num);//将字符串转换成整数
//取高位字节
zeromery(num,1024);
ptr1=ptr2;
ptr2--;
while(*(ptr2)!=',')
ptr2--;
strncpy(num,ptr2+1,ptr1-ptr2-1);
j=atoi(num);
//初始化服务器数据连接时的端口信息
int data_serviceport;
data_serviceport=j*256+i;
data_sockfd=socket(AF_INET,SOCK_STREAM,0);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=INADDR_ANY;
serv_addr.sin_port=htons(data_serviceport);
if(connect(data_sockfd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr))==-1)
{
printf("pasv data connect is error!/n");
}
printf("remote-file-pathname=");
scanf("%s",pathname);
printf("local-file-pathname=");
scanf("%s",localpathname);
printf("local:%s remore:%s/n",localpathname,pathname);
fd=fopen(localpathname,"w+");
if(fd==NULL)
{
printf("cannot open file/n");
exit(1);
}
//send the command retr;
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"RETR ");
strcat(sendline,pathname);
strcat(sendline,"/r/n");
printf("%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("retr send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("retr recv is error!/n");
}
if(strncmp(recvline,"400",3)>0)
{
printf("return is error!/n");
}
}
//port mode
/*if(pasv_or_port==1)
{
data_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(data_sockfd<0)
{
printf("创建数据端口连接失败!/n");
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=INADDR_ANY;
serv_addr.sin_port=htons(SERV_PORT);
int ret;
int addrlen;
ret=sizeof(struct sockaddr_in);
getsockname(data_sockfd,(SA*)&serv_addr,&ret);
//处理port 后面要带的参数
char ip[1024];
int i,j;
char data[1024];
zeromery(ip,1024);
zeromery(data,1024);
inet_ntop(AF_INET,&(serv_addr.sin_addr),ip,sizeof(ip));
printf("%s/n",ip);
i=data_sockfd/256;
j=data_sockfd%256;
//将点分十进制的点转换为逗号。
char *ptr1;
ptr1=ip;
while(*(ptr1)!='/0')
{
if(*(ptr1)=='.')
{
*(ptr1)=',';
}
ptr1++;
}
strcat(sendline,"PORT ");
strcat(sendline,ip);
strcat(sendline,",");
strcat(sendline,itoa(i,data,10));
strcat(sendline,",");
strcat(sendline,itoa(j,data,10));
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("port send is error!/n");
exit(1);
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"200",3)==0)
{
printf("%s/n",recv);
}
else
{
printf("port recv is error!/n");
}
}*/
//begin to transpotr data
sleep(1);
int flag=0;
char buffer[65536];
recvdata:
zeromery(buffer,1024);
recvbytes=recv(data_sockfd,buffer,sizeof(buffer),0);
if(recvbytes<0)
{
close(data_sockfd);
goto end;
}
fwrite(buffer,1,recvbytes,fd);
zeromery(recvline,1024);
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(flag==0)
{
if(strncmp(recvline,"226",3)!=0)
{
flag=1;
goto recvdata;
}
}
end:
if(flag!=1)
{
zeromery(recvline,1024);
shutdown(data_sockfd,SHUT_WR);
close(data_sockfd);
}
close(data_sockfd);
/* int err;
char buffer[65535];
err=read(data_sockfd,buffer,sizeof(buffer));
sleep(5);
fwrite(buffer,1,err,fd);
sleep(5);
zeromery(recvline,1024);
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"226",3)==0)
{
end: printf("226 transfer complete/n");
close(data_sockfd);
}*/
return 0;
}
}
//up 函数
int ftp_up(int control_sockfd)
{
int pasv_or_port;// 定义the ftp协议的两种不同工作mode
int recvbytes,sendbytes;
char sendline[1024],recvline[1024];
struct sockaddr_in serv_addr;
FILE *fd;
int i,j;
int data_sockfd;
//type
zeromery(recvline,1024);
zeromery(sendline,1024);
strcat(sendline,"TYPE ");
strcat(sendline,"I");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf(" type send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"200",3)==0)
{
printf("使用二进制传输数据/n");
}
else
{
printf("type recv is error!/n");
}
if(npsupport==1)
{
//open the file
int size;
char localpathname[60];//预打开的文件路径字符串
int flags;
char pathname[60];
unsigned int mode;
//用户来选择pasv 或者是 port mode
char selectdata_mode_tran[1024];
zeromery(selectdata_mode_tran,1024);
zeromery(sendline,1024);
zeromery(recvline,1024);
pasv_or_port=0;//(默认是pasv模式)
//pasv mode
if(pasv_or_port==0)
{
strcat(sendline,"PASV");
strcat(sendline,"/r/n");
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("pasv send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("pasv recv is error!/n");
}
if(strncmp(recvline,"227",3)==0)
{
char buf[55];
snprintf(buf,51,">>> %s/n",recvline);
printf("%s/n",buf);
}
else
{
printf("pasv recv is error!/n");
}
//处理ftp server 端口
char *ptr1,*ptr2;
char num[1024];
zeromery(num,1024);
//取低位字节
ptr1=recvline+strlen(recvline);
while(*(ptr1)!=')')
{
ptr1--;
}
ptr2=ptr1;
while(*(ptr2)!=',')
ptr2--;
strncpy(num,ptr2+1,ptr1-ptr2-1);
i=atoi(num);//将字符串转换成整数
//取高位字节
zeromery(num,1024);
ptr1=ptr2;
ptr2--;
while(*(ptr2)!=',')
ptr2--;
strncpy(num,ptr2+1,ptr1-ptr2-1);
j=atoi(num);
//初始化服务器数据连接时的端口信息
int data_serviceport;
data_serviceport=j*256+i;
data_sockfd=socket(AF_INET,SOCK_STREAM,0);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=INADDR_ANY;
serv_addr.sin_port=htons(data_serviceport);
if(connect(data_sockfd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr))==-1)
{
printf("pasv data connect is error!/n");
}
printf("local-file-pathname=");
scanf("%s",pathname);
printf("remote-file-pathname=");
scanf("%s",localpathname);
printf("local:%s remore:%s/n",localpathname,pathname);
fd=fopen(pathname,"r");
if(fd==NULL)
{
printf("cannot open file,请重新输入!/n");
}
//send the command retr;
zeromery(sendline,1024);
zeromery(recvline,1024);
strcat(sendline,"STOR ");
strcat(sendline,localpathname);
strcat(sendline,"/r/n");
printf("%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("stor send is error!/n");
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(recvbytes<0)
{
printf("retr recv is error!/n");
}
if(strncmp(recvline,"150",3)==0)
{
char buf[55];
snprintf(buf,25,">>> %s/n",recvline);
printf("%s/n",buf);
}
}
//port mode
/*if(pasv_or_port==1)
{
data_sockfd=socket(AF_INET,SOCK_STREAM,0);
if(data_sockfd<0)
{
printf("创建数据端口连接失败!/n");
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=INADDR_ANY;
serv_addr.sin_port=htons(SERV_PORT);
int ret;
int addrlen;
ret=sizeof(struct sockaddr_in);
getsockname(data_sockfd,(SA*)&serv_addr,&ret);
//处理port 后面要带的参数
char ip[1024];
int i,j;
char data[1024];
zeromery(ip,1024);
zeromery(data,1024);
inet_ntop(AF_INET,&(serv_addr.sin_addr),ip,sizeof(ip));
printf("%s/n",ip);
i=data_sockfd/256;
j=data_sockfd%256;
//将点分十进制的点转换为逗号。
char *ptr1;
ptr1=ip;
while(*(ptr1)!='/0')
{
if(*(ptr1)=='.')
{
*(ptr1)=',';
}
ptr1++;
}
strcat(sendline,"PORT ");
strcat(sendline,ip);
strcat(sendline,",");
strcat(sendline,itoa(i,data,10));
strcat(sendline,",");
strcat(sendline,itoa(j,data,10));
strcat(sendline,"/r/n");
printf("--->%s/n",sendline);
sendbytes=send(control_sockfd,sendline,strlen(sendline),0);
if(sendbytes<0)
{
printf("port send is error!/n");
exit(1);
}
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"200",3)==0)
{
printf("%s/n",recv);
}
else
{
printf("port recv is error!/n");
}
}*/
//begin to transpotr data
while(!feof(fd))
{
char buffer[65536];
zeromery(buffer,sizeof(buffer));
int size;
size=fread(buffer,1,sizeof(buffer),fd);
if(ferror(fd))
{
printf("read file data is error!/n");
break;
}
else
{
zeromery(sendline,1024);
sendbytes=send(data_sockfd,buffer,size,0);
printf("传输了 %d 个字节/n",sendbytes);
}
close(data_sockfd);
recvbytes=recv(control_sockfd,recvline,sizeof(recvline),0);
if(strncmp(recvline,"226",3)==0)
{
printf("226 transfer complete");
break;
}
}
return 0;
}
}